mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-26 14:46:42 +00:00
rework throttle mechanism: calculate an EWMA of the gasUsed/blockSize of recently-completed results. use this in conjunction with avg gas used of in-flight results to estimate the average block size and how many to schedule to fit within the block cache mem limit.
This commit is contained in:
parent
a2b5b3c1b7
commit
52221ae8cf
2 changed files with 48 additions and 17 deletions
|
|
@ -138,8 +138,9 @@ type queue struct {
|
||||||
receiptPendPool map[string]*fetchRequest // Currently pending receipt retrieval operations
|
receiptPendPool map[string]*fetchRequest // Currently pending receipt retrieval operations
|
||||||
receiptWakeCh chan bool // Channel to notify when receipt fetcher of new tasks
|
receiptWakeCh chan bool // Channel to notify when receipt fetcher of new tasks
|
||||||
|
|
||||||
resultCache *resultStore // Downloaded but not yet delivered fetch results
|
resultCache *resultStore // Downloaded but not yet delivered fetch results
|
||||||
resultSize common.StorageSize // Approximate size of a block (exponential moving average)
|
resultSize common.StorageSize // Approximate size of a block (exponential moving average)
|
||||||
|
resultGasSizeRatio common.StorageSize // gas used vs block size (bytes) EWMA
|
||||||
|
|
||||||
lock *sync.RWMutex
|
lock *sync.RWMutex
|
||||||
active *sync.Cond
|
active *sync.Cond
|
||||||
|
|
@ -180,8 +181,7 @@ func (q *queue) Reset(blockCacheLimit int, thresholdInitialSize int) {
|
||||||
q.receiptTaskQueue.Reset()
|
q.receiptTaskQueue.Reset()
|
||||||
q.receiptPendPool = make(map[string]*fetchRequest)
|
q.receiptPendPool = make(map[string]*fetchRequest)
|
||||||
|
|
||||||
q.resultCache = newResultStore(blockCacheLimit)
|
q.resultCache = newResultStore(blockCacheLimit, thresholdInitialSize)
|
||||||
q.resultCache.SetThrottleThreshold(uint64(thresholdInitialSize))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close marks the end of the sync, unblocking Results.
|
// Close marks the end of the sync, unblocking Results.
|
||||||
|
|
@ -327,11 +327,12 @@ func (q *queue) Results(block bool) []*fetchResult {
|
||||||
size += common.StorageSize(result.Withdrawals.Size())
|
size += common.StorageSize(result.Withdrawals.Size())
|
||||||
q.resultSize = common.StorageSize(blockCacheSizeWeight)*size +
|
q.resultSize = common.StorageSize(blockCacheSizeWeight)*size +
|
||||||
(1-common.StorageSize(blockCacheSizeWeight))*q.resultSize
|
(1-common.StorageSize(blockCacheSizeWeight))*q.resultSize
|
||||||
|
|
||||||
|
// TODO: do we need to keep the resultSize EWMA above? it's only used in the metrics rn
|
||||||
|
q.resultGasSizeRatio = common.StorageSize(blockCacheSizeWeight)*(common.StorageSize(result.Header.GasUsed)/size) +
|
||||||
|
(1-common.StorageSize(blockCacheSizeWeight))*q.resultGasSizeRatio
|
||||||
}
|
}
|
||||||
// Using the newly calibrated result size, figure out the new throttle limit
|
throttleThreshold := q.resultCache.SetThrottleTarget(q.resultGasSizeRatio)
|
||||||
// on the result cache
|
|
||||||
throttleThreshold := uint64((common.StorageSize(blockCacheMemory) + q.resultSize - 1) / q.resultSize)
|
|
||||||
throttleThreshold = q.resultCache.SetThrottleThreshold(throttleThreshold)
|
|
||||||
|
|
||||||
// With results removed from the cache, wake throttled fetchers
|
// With results removed from the cache, wake throttled fetchers
|
||||||
for _, ch := range []chan bool{q.blockWakeCh, q.receiptWakeCh} {
|
for _, ch := range []chan bool{q.blockWakeCh, q.receiptWakeCh} {
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ package downloader
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
|
|
@ -46,28 +47,49 @@ type resultStore struct {
|
||||||
// contained in items.
|
// contained in items.
|
||||||
pendingCount int
|
pendingCount int
|
||||||
|
|
||||||
|
// pendingGasUsed tracks the current total gas used of all in-flight bodies
|
||||||
|
pendingGasUsed uint64
|
||||||
|
|
||||||
|
targetGasScheduled uint64
|
||||||
|
|
||||||
lock sync.RWMutex
|
lock sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func newResultStore(size int) *resultStore {
|
func newResultStore(size, throttleThreshold int) *resultStore {
|
||||||
return &resultStore{
|
return &resultStore{
|
||||||
resultOffset: 0,
|
resultOffset: 0,
|
||||||
items: make([]*fetchResult, size),
|
items: make([]*fetchResult, size),
|
||||||
throttleThreshold: uint64(size),
|
throttleThreshold: uint64(throttleThreshold),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetThrottleThreshold updates the throttling threshold based on the requested
|
// SetThrottleTarget updates the throttling threshold based on a targetRatio of gas used / block size,
|
||||||
// limit and the total queue capacity. It returns the (possibly capped) threshold
|
// using that to estimate the average block size based on current in-flight retrievals and returning
|
||||||
func (r *resultStore) SetThrottleThreshold(threshold uint64) uint64 {
|
// the throttle threshold: how many blocks of the estimated size will fit within the block cache
|
||||||
|
// capacity.
|
||||||
|
func (r *resultStore) SetThrottleTarget(targetRatio common.StorageSize) uint64 {
|
||||||
r.lock.Lock()
|
r.lock.Lock()
|
||||||
defer r.lock.Unlock()
|
defer r.lock.Unlock()
|
||||||
|
|
||||||
limit := uint64(len(r.items))
|
if r.pendingCount == 0 {
|
||||||
if threshold >= limit {
|
return r.throttleThreshold
|
||||||
threshold = limit
|
|
||||||
}
|
}
|
||||||
r.throttleThreshold = threshold
|
if targetRatio == 0 {
|
||||||
|
targetRatio = 0.1 // TODO: more informed choice of a minimum here?
|
||||||
|
}
|
||||||
|
// use the target ratio to determine
|
||||||
|
// (pendingGasUsed / target) <- estimated total size of collective in-flight retrievals
|
||||||
|
estBlockSize := common.StorageSize(r.pendingGasUsed/uint64(r.pendingCount)) / targetRatio
|
||||||
|
|
||||||
|
// if pending block(s) have 0 gas used, the estimated block size is just a header
|
||||||
|
estBlockSize = max(estBlockSize, 524)
|
||||||
|
targetRetrievalCount := blockCacheMemory / uint64(estBlockSize)
|
||||||
|
|
||||||
|
limit := uint64(len(r.items))
|
||||||
|
if targetRetrievalCount >= limit {
|
||||||
|
targetRetrievalCount = limit
|
||||||
|
}
|
||||||
|
r.throttleThreshold = targetRetrievalCount
|
||||||
return r.throttleThreshold
|
return r.throttleThreshold
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -90,9 +112,14 @@ func (r *resultStore) AddFetch(header *types.Header, snapSync bool) (stale, thro
|
||||||
return stale, throttled, item, err
|
return stale, throttled, item, err
|
||||||
}
|
}
|
||||||
if item == nil {
|
if item == nil {
|
||||||
|
if header.GasUsed+r.pendingGasUsed > r.targetGasScheduled {
|
||||||
|
return false, true, nil, nil
|
||||||
|
}
|
||||||
item = newFetchResult(header, snapSync)
|
item = newFetchResult(header, snapSync)
|
||||||
r.items[index] = item
|
r.items[index] = item
|
||||||
r.pendingCount++
|
r.pendingCount++
|
||||||
|
pendingBodyGauge.Inc(1)
|
||||||
|
r.pendingGasUsed += header.GasUsed
|
||||||
}
|
}
|
||||||
return stale, throttled, item, err
|
return stale, throttled, item, err
|
||||||
}
|
}
|
||||||
|
|
@ -176,6 +203,9 @@ func (r *resultStore) GetCompleted(limit int) []*fetchResult {
|
||||||
}
|
}
|
||||||
|
|
||||||
r.pendingCount -= limit
|
r.pendingCount -= limit
|
||||||
|
for i := 0; i < limit; i++ {
|
||||||
|
r.pendingGasUsed -= r.items[i].Header.GasUsed
|
||||||
|
}
|
||||||
pendingBodyGauge.Update(int64(r.pendingCount))
|
pendingBodyGauge.Update(int64(r.pendingCount))
|
||||||
|
|
||||||
results := make([]*fetchResult, limit)
|
results := make([]*fetchResult, limit)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue