perf: Address performance of EthGetTransactionCount
We have observed that EthGetTransactionCount is one of the hotspots on Glif production notes, and we are seeing regular 10-20 second latencies when calling this rpc method. I tracked the high latency spikes and they were correlated when we were running ExecuteTipSet while following the chain. To address this, we should not rely on tipset computation to get nounce and instead look at the parent tipset and then count the messages sent from the 'addr'.
This commit is contained in:
parent
d1d4b35adf
commit
3832dc9208
@ -169,13 +169,13 @@ type MessagePool struct {
|
|||||||
|
|
||||||
sigValCache *lru.TwoQueueCache[string, struct{}]
|
sigValCache *lru.TwoQueueCache[string, struct{}]
|
||||||
|
|
||||||
nonceCache *lru.Cache[nonceCacheKey, uint64]
|
stateNonceCache *lru.Cache[stateNonceCacheKey, uint64]
|
||||||
|
|
||||||
evtTypes [3]journal.EventType
|
evtTypes [3]journal.EventType
|
||||||
journal journal.Journal
|
journal journal.Journal
|
||||||
}
|
}
|
||||||
|
|
||||||
type nonceCacheKey struct {
|
type stateNonceCacheKey struct {
|
||||||
tsk types.TipSetKey
|
tsk types.TipSetKey
|
||||||
addr address.Address
|
addr address.Address
|
||||||
}
|
}
|
||||||
@ -371,7 +371,7 @@ func (ms *msgSet) toSlice() []*types.SignedMessage {
|
|||||||
func New(ctx context.Context, api Provider, ds dtypes.MetadataDS, us stmgr.UpgradeSchedule, netName dtypes.NetworkName, j journal.Journal) (*MessagePool, error) {
|
func New(ctx context.Context, api Provider, ds dtypes.MetadataDS, us stmgr.UpgradeSchedule, netName dtypes.NetworkName, j journal.Journal) (*MessagePool, error) {
|
||||||
cache, _ := lru.New2Q[cid.Cid, crypto.Signature](build.BlsSignatureCacheSize)
|
cache, _ := lru.New2Q[cid.Cid, crypto.Signature](build.BlsSignatureCacheSize)
|
||||||
verifcache, _ := lru.New2Q[string, struct{}](build.VerifSigCacheSize)
|
verifcache, _ := lru.New2Q[string, struct{}](build.VerifSigCacheSize)
|
||||||
noncecache, _ := lru.New[nonceCacheKey, uint64](256)
|
stateNonceCache, _ := lru.New[stateNonceCacheKey, uint64](256)
|
||||||
|
|
||||||
cfg, err := loadConfig(ctx, ds)
|
cfg, err := loadConfig(ctx, ds)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -397,7 +397,7 @@ func New(ctx context.Context, api Provider, ds dtypes.MetadataDS, us stmgr.Upgra
|
|||||||
pruneCooldown: make(chan struct{}, 1),
|
pruneCooldown: make(chan struct{}, 1),
|
||||||
blsSigCache: cache,
|
blsSigCache: cache,
|
||||||
sigValCache: verifcache,
|
sigValCache: verifcache,
|
||||||
nonceCache: noncecache,
|
stateNonceCache: stateNonceCache,
|
||||||
changes: lps.New(50),
|
changes: lps.New(50),
|
||||||
localMsgs: namespace.Wrap(ds, datastore.NewKey(localMsgsDs)),
|
localMsgs: namespace.Wrap(ds, datastore.NewKey(localMsgsDs)),
|
||||||
api: api,
|
api: api,
|
||||||
@ -1046,24 +1046,52 @@ func (mp *MessagePool) getStateNonce(ctx context.Context, addr address.Address,
|
|||||||
done := metrics.Timer(ctx, metrics.MpoolGetNonceDuration)
|
done := metrics.Timer(ctx, metrics.MpoolGetNonceDuration)
|
||||||
defer done()
|
defer done()
|
||||||
|
|
||||||
nk := nonceCacheKey{
|
nk := stateNonceCacheKey{
|
||||||
tsk: ts.Key(),
|
tsk: ts.Key(),
|
||||||
addr: addr,
|
addr: addr,
|
||||||
}
|
}
|
||||||
|
|
||||||
n, ok := mp.nonceCache.Get(nk)
|
n, ok := mp.stateNonceCache.Get(nk)
|
||||||
if ok {
|
if ok {
|
||||||
return n, nil
|
return n, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
act, err := mp.api.GetActorAfter(addr, ts)
|
raddr, err := mp.resolveToKey(ctx, addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
mp.nonceCache.Add(nk, act.Nonce)
|
// get the nonce from the actor before ts
|
||||||
|
actor, err := mp.api.GetActorBefore(addr, ts)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
nextNonce := actor.Nonce
|
||||||
|
|
||||||
return act.Nonce, nil
|
// loop over all messages sent by 'addr' and find the highest nonce
|
||||||
|
messages, err := mp.api.MessagesForTipset(ctx, ts)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
for _, message := range messages {
|
||||||
|
msg := message.VMMessage()
|
||||||
|
|
||||||
|
maddr, err := mp.resolveToKey(ctx, msg.From)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("failed to resolve message from address: %s", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if maddr == raddr {
|
||||||
|
if n := msg.Nonce + 1; n > nextNonce {
|
||||||
|
nextNonce = n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mp.stateNonceCache.Add(nk, nextNonce)
|
||||||
|
|
||||||
|
return nextNonce, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mp *MessagePool) getStateBalance(ctx context.Context, addr address.Address, ts *types.TipSet) (types.BigInt, error) {
|
func (mp *MessagePool) getStateBalance(ctx context.Context, addr address.Address, ts *types.TipSet) (types.BigInt, error) {
|
||||||
|
@ -120,6 +120,22 @@ func (tma *testMpoolAPI) PubSubPublish(string, []byte) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (tma *testMpoolAPI) GetActorBefore(addr address.Address, ts *types.TipSet) (*types.Actor, error) {
|
||||||
|
balance, ok := tma.balance[addr]
|
||||||
|
if !ok {
|
||||||
|
balance = types.NewInt(1000e6)
|
||||||
|
tma.balance[addr] = balance
|
||||||
|
}
|
||||||
|
|
||||||
|
nonce := tma.statenonce[addr]
|
||||||
|
|
||||||
|
return &types.Actor{
|
||||||
|
Code: builtin2.AccountActorCodeID,
|
||||||
|
Nonce: nonce,
|
||||||
|
Balance: balance,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (tma *testMpoolAPI) GetActorAfter(addr address.Address, ts *types.TipSet) (*types.Actor, error) {
|
func (tma *testMpoolAPI) GetActorAfter(addr address.Address, ts *types.TipSet) (*types.Actor, error) {
|
||||||
// regression check for load bug
|
// regression check for load bug
|
||||||
if ts == nil {
|
if ts == nil {
|
||||||
|
@ -2,6 +2,7 @@ package messagepool
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ipfs/go-cid"
|
"github.com/ipfs/go-cid"
|
||||||
@ -27,6 +28,7 @@ type Provider interface {
|
|||||||
SubscribeHeadChanges(func(rev, app []*types.TipSet) error) *types.TipSet
|
SubscribeHeadChanges(func(rev, app []*types.TipSet) error) *types.TipSet
|
||||||
PutMessage(ctx context.Context, m types.ChainMsg) (cid.Cid, error)
|
PutMessage(ctx context.Context, m types.ChainMsg) (cid.Cid, error)
|
||||||
PubSubPublish(string, []byte) error
|
PubSubPublish(string, []byte) error
|
||||||
|
GetActorBefore(address.Address, *types.TipSet) (*types.Actor, error)
|
||||||
GetActorAfter(address.Address, *types.TipSet) (*types.Actor, error)
|
GetActorAfter(address.Address, *types.TipSet) (*types.Actor, error)
|
||||||
StateDeterministicAddressAtFinality(context.Context, address.Address, *types.TipSet) (address.Address, error)
|
StateDeterministicAddressAtFinality(context.Context, address.Address, *types.TipSet) (address.Address, error)
|
||||||
StateNetworkVersion(context.Context, abi.ChainEpoch) network.Version
|
StateNetworkVersion(context.Context, abi.ChainEpoch) network.Version
|
||||||
@ -58,6 +60,23 @@ func (mpp *mpoolProvider) IsLite() bool {
|
|||||||
return mpp.lite != nil
|
return mpp.lite != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (mpp *mpoolProvider) getActorLite(addr address.Address, ts *types.TipSet) (*types.Actor, error) {
|
||||||
|
if !mpp.IsLite() {
|
||||||
|
return nil, errors.New("should not use getActorLite on non lite Provider")
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err := mpp.lite.GetNonce(context.TODO(), addr, ts.Key())
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Errorf("getting nonce over lite: %w", err)
|
||||||
|
}
|
||||||
|
a, err := mpp.lite.GetActor(context.TODO(), addr, ts.Key())
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Errorf("getting actor over lite: %w", err)
|
||||||
|
}
|
||||||
|
a.Nonce = n
|
||||||
|
return a, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (mpp *mpoolProvider) SubscribeHeadChanges(cb func(rev, app []*types.TipSet) error) *types.TipSet {
|
func (mpp *mpoolProvider) SubscribeHeadChanges(cb func(rev, app []*types.TipSet) error) *types.TipSet {
|
||||||
mpp.sm.ChainStore().SubscribeHeadChanges(
|
mpp.sm.ChainStore().SubscribeHeadChanges(
|
||||||
store.WrapHeadChangeCoalescer(
|
store.WrapHeadChangeCoalescer(
|
||||||
@ -77,18 +96,17 @@ func (mpp *mpoolProvider) PubSubPublish(k string, v []byte) error {
|
|||||||
return mpp.ps.Publish(k, v) // nolint
|
return mpp.ps.Publish(k, v) // nolint
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (mpp *mpoolProvider) GetActorBefore(addr address.Address, ts *types.TipSet) (*types.Actor, error) {
|
||||||
|
if mpp.IsLite() {
|
||||||
|
return mpp.getActorLite(addr, ts)
|
||||||
|
}
|
||||||
|
|
||||||
|
return mpp.sm.LoadActor(context.TODO(), addr, ts)
|
||||||
|
}
|
||||||
|
|
||||||
func (mpp *mpoolProvider) GetActorAfter(addr address.Address, ts *types.TipSet) (*types.Actor, error) {
|
func (mpp *mpoolProvider) GetActorAfter(addr address.Address, ts *types.TipSet) (*types.Actor, error) {
|
||||||
if mpp.IsLite() {
|
if mpp.IsLite() {
|
||||||
n, err := mpp.lite.GetNonce(context.TODO(), addr, ts.Key())
|
return mpp.getActorLite(addr, ts)
|
||||||
if err != nil {
|
|
||||||
return nil, xerrors.Errorf("getting nonce over lite: %w", err)
|
|
||||||
}
|
|
||||||
a, err := mpp.lite.GetActor(context.TODO(), addr, ts.Key())
|
|
||||||
if err != nil {
|
|
||||||
return nil, xerrors.Errorf("getting actor over lite: %w", err)
|
|
||||||
}
|
|
||||||
a.Nonce = n
|
|
||||||
return a, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
stcid, _, err := mpp.sm.TipSetState(context.TODO(), ts)
|
stcid, _, err := mpp.sm.TipSetState(context.TODO(), ts)
|
||||||
|
Loading…
Reference in New Issue
Block a user