From 7f68130f59fad3e289f6254460573280b87bd7ae Mon Sep 17 00:00:00 2001 From: kewl fft Date: Sat, 31 May 2025 15:45:17 +0100 Subject: [PATCH 1/2] Use EnableRandomization from config in HealthcheckManager --- example_config.yml | 10 ++++++++-- internal/proxy/config.go | 3 ++- internal/proxy/manager.go | 10 +++++++++- internal/rpcgateway/rpcgateway.go | 1 + 4 files changed, 20 insertions(+), 4 deletions(-) diff --git a/example_config.yml b/example_config.yml index 13578f9..1fe2b6e 100644 --- a/example_config.yml +++ b/example_config.yml @@ -4,6 +4,7 @@ metrics: proxy: port: 3000 # port for RPC gateway upstreamTimeout: "1s" # when is a request considered timed out + enableRandomization: true healthChecks: interval: "5s" # how often to do healthchecks @@ -12,14 +13,19 @@ healthChecks: successThreshold: 1 # how many successes to be marked as healthy again targets: - - name: "QuickNode" + - name: "Blastapi" connection: http: # ws is supported by default, it will be a sticky connection. - url: "https://rpc.ankr.com/eth" + url: "https://eth-mainnet.public.blastapi.io" # compression: true # Specify if the target supports request compression # optional ws url for Solana configuration ws: url: "wss://solana.ws.node" + - name: "Flashbots" + connection: + http: + url: "https://rpc.flashbots.net/" + exceptions: # String to match in the response body diff --git a/internal/proxy/config.go b/internal/proxy/config.go index 271f9fd..d54f0ac 100644 --- a/internal/proxy/config.go +++ b/internal/proxy/config.go @@ -11,9 +11,10 @@ type HealthCheckConfig struct { SuccessThreshold uint `yaml:"successThreshold"` } -type ProxyConfig struct { // nolint:revive +type ProxyConfig struct { Port string `yaml:"port"` UpstreamTimeout time.Duration `yaml:"upstreamTimeout"` + EnableRandomization bool `yaml:"enableRandomization"` } type TargetConnectionHTTP struct { diff --git a/internal/proxy/manager.go b/internal/proxy/manager.go index b597b06..66c7a00 100644 --- a/internal/proxy/manager.go +++ b/internal/proxy/manager.go @@ -17,6 +17,7 @@ type HealthcheckManagerConfig struct { Targets []TargetConfig Config HealthCheckConfig Solana bool + Proxy ProxyConfig } type HealthcheckManager struct { @@ -27,12 +28,15 @@ type HealthcheckManager struct { metricResponseTime *prometheus.HistogramVec metricRPCProviderBlockNumber *prometheus.GaugeVec metricRPCProviderGasLimit *prometheus.GaugeVec + + enableRandomization bool } func NewHealthcheckManager(config HealthcheckManagerConfig) *HealthcheckManager { healthCheckers := []Healthchecker{} healthcheckManager := &HealthcheckManager{ + enableRandomization: config.Proxy.EnableRandomization, metricRPCProviderInfo: promauto.NewGaugeVec( prometheus.GaugeOpts{ Name: "zeroex_rpc_gateway_provider_info", @@ -211,7 +215,11 @@ func (h *HealthcheckManager) GetNextHealthyTargetIndexExcluding(excludedIdx []ui return -1 } - idx := rand.Intn(totalTargets) + idx := 0 + if h.enableRandomization { + idx = rand.Intn(totalTargets) + } + delta := 0 for delta < totalTargets { adjustedIndex := (idx + delta) % totalTargets diff --git a/internal/rpcgateway/rpcgateway.go b/internal/rpcgateway/rpcgateway.go index 9c9cab9..f8af850 100644 --- a/internal/rpcgateway/rpcgateway.go +++ b/internal/rpcgateway/rpcgateway.go @@ -98,6 +98,7 @@ func NewRPCGateway(config RPCGatewayConfig) *RPCGateway { proxy.HealthcheckManagerConfig{ Targets: config.Targets, Config: config.HealthChecks, + Proxy: config.Proxy, Solana: config.Solana, }) httpFailoverProxy := proxy.NewProxy( From 3b91edc3617deef6a3ed63ede01fdf363c01b464 Mon Sep 17 00:00:00 2001 From: kewl fft Date: Sat, 31 May 2025 16:29:14 +0100 Subject: [PATCH 2/2] optimize iteration over all targets --- internal/proxy/manager.go | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/internal/proxy/manager.go b/internal/proxy/manager.go index 66c7a00..14e8a62 100644 --- a/internal/proxy/manager.go +++ b/internal/proxy/manager.go @@ -6,8 +6,6 @@ import ( "strconv" "time" - "slices" - "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" "go.uber.org/zap" @@ -208,29 +206,36 @@ func (h *HealthcheckManager) GetNextHealthyTargetIndex() int { } func (h *HealthcheckManager) GetNextHealthyTargetIndexExcluding(excludedIdx []uint) int { - totalTargets := len(h.healthcheckers) if totalTargets == 0 { zap.L().Error("no targets") return -1 } - idx := 0 + // Convert excludedIdx slice to a set for O(1) lookups + excluded := make(map[uint]struct{}, len(excludedIdx)) + for _, i := range excludedIdx { + excluded[i] = struct{}{} + } + + // Choose random starting index if enabled + startIdx := 0 if h.enableRandomization { - idx = rand.Intn(totalTargets) + startIdx = rand.Intn(totalTargets) } - delta := 0 - for delta < totalTargets { - adjustedIndex := (idx + delta) % totalTargets - target := h.healthcheckers[adjustedIndex] - if !slices.Contains(excludedIdx, uint(adjustedIndex)) && target.IsHealthy() { - return adjustedIndex + // Iterate over all targets once + for i := 0; i < totalTargets; i++ { + idx := (startIdx + i) % totalTargets + if _, skip := excluded[uint(idx)]; skip { + continue + } + if h.healthcheckers[idx].IsHealthy() { + return idx } - delta++ } - // no healthy targets, we down:( zap.L().Error("no more healthy targets") return -1 } +