Compare commits

..

2 Commits

Author SHA1 Message Date
colin
35d4ec5ad0 feat(bridge-history): add cache hit metrics and cache non-existent keys (#970)
Co-authored-by: colinlyguo <colinlyguo@users.noreply.github.com>
2023-09-26 23:43:09 +08:00
colin
8f745e9836 fix(bridge-history): duplicated symbol and metric prefix (#969)
Co-authored-by: colinlyguo <colinlyguo@users.noreply.github.com>
2023-09-26 18:52:18 +08:00
10 changed files with 74 additions and 69 deletions

View File

@@ -11,10 +11,6 @@ var (
HistoryCtrler *HistoryController
// BatchCtrler is controller instance
BatchCtrler *BatchController
// HealthCheck the health check controller
HealthCheck *HealthCheckController
// Ready the ready controller
Ready *ReadyController
initControllerOnce sync.Once
)
@@ -24,7 +20,5 @@ func InitController(db *gorm.DB) {
initControllerOnce.Do(func() {
HistoryCtrler = NewHistoryController(db)
BatchCtrler = NewBatchController(db)
HealthCheck = NewHealthCheckController(db)
Ready = NewReadyController()
})
}

View File

@@ -1,30 +0,0 @@
package controller
import (
"github.com/gin-gonic/gin"
"gorm.io/gorm"
"bridge-history-api/internal/types"
"bridge-history-api/utils"
)
// HealthCheckController is health check API
type HealthCheckController struct {
db *gorm.DB
}
// NewHealthCheckController returns an HealthCheckController instance
func NewHealthCheckController(db *gorm.DB) *HealthCheckController {
return &HealthCheckController{
db: db,
}
}
// HealthCheck the api controller for coordinator health check
func (a *HealthCheckController) HealthCheck(c *gin.Context) {
if _, err := utils.Ping(a.db); err != nil {
types.RenderFatal(c, err)
return
}
types.RenderSuccess(c, nil)
}

View File

@@ -26,6 +26,7 @@ type HistoryController struct {
historyLogic *logic.HistoryLogic
cache *cache.Cache
singleFlight singleflight.Group
cacheMetrics *cacheMetrics
}
// NewHistoryController return HistoryController instance
@@ -33,6 +34,7 @@ func NewHistoryController(db *gorm.DB) *HistoryController {
return &HistoryController{
historyLogic: logic.NewHistoryLogic(db),
cache: cache.New(30*time.Second, 10*time.Minute),
cacheMetrics: initCacheMetrics(),
}
}
@@ -46,12 +48,18 @@ func (c *HistoryController) GetAllClaimableTxsByAddr(ctx *gin.Context) {
cacheKey := cacheKeyPrefixClaimableTxsByAddr + req.Address
if cachedData, found := c.cache.Get(cacheKey); found {
if resultData, ok := cachedData.(*types.ResultData); ok {
c.cacheMetrics.cacheHits.WithLabelValues("GetAllClaimableTxsByAddr").Inc()
if cachedData == nil {
types.RenderSuccess(ctx, &types.ResultData{})
return
} else if resultData, ok := cachedData.(*types.ResultData); ok {
types.RenderSuccess(ctx, resultData)
return
}
// Log error for unexpected type, then fetch data from the database.
log.Error("unexpected type in cache", "expected", "*types.ResultData", "got", reflect.TypeOf(cachedData))
} else {
c.cacheMetrics.cacheMisses.WithLabelValues("GetAllClaimableTxsByAddr").Inc()
}
result, err, _ := c.singleFlight.Do(cacheKey, func() (interface{}, error) {
@@ -86,7 +94,7 @@ func (c *HistoryController) PostQueryTxsByHash(ctx *gin.Context) {
}
if len(req.Txs) > 10 {
types.RenderFailure(ctx, types.ErrParameterInvalidNo, errors.New("the number of hashes in the request exceeds the allowed maximum"))
types.RenderFailure(ctx, types.ErrParameterInvalidNo, errors.New("the number of hashes in the request exceeds the allowed maximum of 10"))
return
}
hashesMap := make(map[string]struct{}, len(req.Txs))
@@ -101,13 +109,17 @@ func (c *HistoryController) PostQueryTxsByHash(ctx *gin.Context) {
cacheKey := cacheKeyPrefixQueryTxsByHash + hash
if cachedData, found := c.cache.Get(cacheKey); found {
if txInfo, ok := cachedData.(*types.TxHistoryInfo); ok {
c.cacheMetrics.cacheHits.WithLabelValues("PostQueryTxsByHash").Inc()
if cachedData == nil {
continue
} else if txInfo, ok := cachedData.(*types.TxHistoryInfo); ok {
results = append(results, txInfo)
} else {
log.Error("unexpected type in cache", "expected", "*types.TxHistoryInfo", "got", reflect.TypeOf(cachedData))
uncachedHashes = append(uncachedHashes, hash)
}
} else {
c.cacheMetrics.cacheMisses.WithLabelValues("PostQueryTxsByHash").Inc()
uncachedHashes = append(uncachedHashes, hash)
}
}
@@ -119,10 +131,20 @@ func (c *HistoryController) PostQueryTxsByHash(ctx *gin.Context) {
return
}
resultMap := make(map[string]*types.TxHistoryInfo)
for _, result := range dbResults {
results = append(results, result)
cacheKey := cacheKeyPrefixQueryTxsByHash + result.Hash
c.cache.Set(cacheKey, result, cache.DefaultExpiration)
resultMap[result.Hash] = result
}
for _, hash := range uncachedHashes {
cacheKey := cacheKeyPrefixQueryTxsByHash + hash
result, found := resultMap[hash]
if found {
c.cache.Set(cacheKey, result, cache.DefaultExpiration)
} else {
c.cache.Set(cacheKey, nil, cache.DefaultExpiration)
}
}
}

View File

@@ -0,0 +1,40 @@
package controller
import (
"sync"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
)
type cacheMetrics struct {
cacheHits *prometheus.CounterVec
cacheMisses *prometheus.CounterVec
}
var (
initMetricsOnce sync.Once
cm *cacheMetrics
)
func initCacheMetrics() *cacheMetrics {
initMetricsOnce.Do(func() {
cm = &cacheMetrics{
cacheHits: promauto.NewCounterVec(
prometheus.CounterOpts{
Name: "cache_hits_total",
Help: "The total number of cache hits",
},
[]string{"api"},
),
cacheMisses: promauto.NewCounterVec(
prometheus.CounterOpts{
Name: "cache_misses_total",
Help: "The total number of cache misses",
},
[]string{"api"},
),
}
})
return cm
}

View File

@@ -1,21 +0,0 @@
package controller
import (
"github.com/gin-gonic/gin"
"bridge-history-api/internal/types"
)
// ReadyController ready API
type ReadyController struct {
}
// NewReadyController returns an ReadyController instance
func NewReadyController() *ReadyController {
return &ReadyController{}
}
// Ready the api controller for coordinator ready
func (r *ReadyController) Ready(c *gin.Context) {
types.RenderSuccess(c, nil)
}

View File

@@ -22,7 +22,7 @@ func Route(router *gin.Engine, conf *config.Config, reg prometheus.Registerer) {
MaxAge: 12 * time.Hour,
}))
observability.Use(router, "bridge-history", reg)
observability.Use(router, "bridge_history", reg)
r := router.Group("api/")
r.POST("/txsbyhashes", controller.HistoryCtrler.PostQueryTxsByHash)

View File

@@ -4,7 +4,7 @@ import (
"github.com/gin-gonic/gin"
"github.com/prometheus/client_golang/prometheus"
"scroll-tech/common/observability/ginmetrics"
"bridge-history-api/observability/ginmetrics"
)
// Use register the gin metric

View File

@@ -4,8 +4,8 @@ import (
"github.com/gin-gonic/gin"
"gorm.io/gorm"
"scroll-tech/common/database"
"scroll-tech/common/types"
"bridge-history-api/internal/types"
"bridge-history-api/utils"
)
// ProbesController probe check controller
@@ -22,7 +22,7 @@ func NewProbesController(db *gorm.DB) *ProbesController {
// HealthCheck the api controller for health check
func (a *ProbesController) HealthCheck(c *gin.Context) {
if _, err := database.Ping(a.db); err != nil {
if _, err := utils.Ping(a.db); err != nil {
types.RenderFatal(c, err)
return
}

View File

@@ -16,7 +16,7 @@ import (
"github.com/urfave/cli/v2"
"gorm.io/gorm"
"scroll-tech/common/utils"
"bridge-history-api/utils"
)
// Server starts the metrics server on the given address, will be closed when the given

View File

@@ -5,7 +5,7 @@ import (
"runtime/debug"
)
var tag = "v4.3.22"
var tag = "v4.3.24"
var commit = func() string {
if info, ok := debug.ReadBuildInfo(); ok {