Skip to main content

Overview

Lasso provides four routing strategies that control how providers are ranked before health-based tiering is applied:
  • load_balanced (default): Random distribution with health tiering
  • fastest: Lowest latency based on method-specific metrics
  • latency_weighted: Probabilistic selection weighted by performance
  • priority: Explicit provider precedence from configuration

Strategy Selection

Select strategy via URL path:
# Load balanced (default)
POST /rpc/:chain
POST /rpc/load-balanced/:chain

# Fastest provider
POST /rpc/fastest/:chain

# Latency weighted
POST /rpc/latency-weighted/:chain

# Profile-aware routing
POST /rpc/profile/:profile/fastest/:chain

Load Balanced

Module: Lasso.RPC.Strategies.LoadBalanced Randomly distributes requests across available providers. After shuffling, health-based tiering reorders providers by circuit breaker state and rate limit status.

Use Cases

  • Maximizing throughput across multiple providers
  • Avoiding rate limits through even distribution
  • Multiple providers of similar quality

Behavior

def rank_channels(channels, _method, _ctx, _profile, _chain) do
  Enum.shuffle(channels)
end
Simple random shuffle with no preference for any provider absent health signals.

Example Traffic Distribution

Scenario: 3 providers (A, B, C)
  • Provider A: Closed circuit, not rate-limited → Tier 1
  • Provider B: Closed circuit, rate-limited → Tier 2
  • Provider C: Half-open circuit → Tier 3
Result: Provider A receives ~100% of traffic as long as it succeeds. This is correct behavior—Lasso routes to the healthiest provider while maintaining fallbacks. To achieve truly even distribution, ensure all providers are in Tier 1 (closed circuit + not rate-limited).

Fastest

Module: Lasso.RPC.Strategies.Fastest Selects the lowest-latency provider for the specific RPC method based on passive benchmarking. Latency is tracked per provider, per method, per transport.

Use Cases

  • Latency is the primary concern
  • Willing to concentrate load on fastest provider
  • Latency-sensitive methods (e.g., eth_getBlockByNumber)

Behavior

Ranks providers by measured latency (ascending):
# Batch fetch all metrics (eliminates N sequential GenServer calls)
requests = Enum.map(channels, fn ch -> {ch.provider_id, method, ch.transport} end)
metrics_map = Metrics.batch_get_transport_performance(profile, chain, requests)

# Sort by latency with staleness and cold start checks
Enum.sort_by(channels, fn channel ->
  case Map.get(metrics_map, key) do
    %{latency_ms: ms, last_updated_ms: updated} ->
      age_ms = current_time - updated
      if age_ms > ctx.freshness_cutoff_ms do
        ctx.cold_start_baseline || 10_000  # Stale - use fallback
      else
        ms  # Fresh - use actual latency
      end
    _ ->
      ctx.cold_start_baseline || 10_000  # Missing - use P75 fallback
  end
end)

Staleness Handling

Metrics older than 10 minutes are considered stale and treated as cold start:
  • Fresh metrics (< 10min): Use actual latency
  • Stale metrics (> 10min): Treat as cold start
  • Missing metrics: Use P75 of known providers as dynamic baseline

Configuration

# Minimum calls for stable metrics (default: 3)
export FASTEST_MIN_CALLS=3

# Minimum success rate filter (default: 0.9)
export FASTEST_MIN_SUCCESS_RATE=0.9

Example

Method: eth_getLogs
ProviderHTTP LatencyWS LatencySelected Transport
Alchemy450ms280msWS
Infura520msN/AHTTP
QuickNode380ms420msHTTP
Result: QuickNode HTTP (380ms) selected first, Alchemy WS (280ms) selected second.
Strategy ranking is applied before health tiering. A fast provider with a half-open circuit will be deprioritized below any closed-circuit provider.

Latency Weighted

Module: Lasso.RPC.Strategies.LatencyWeighted Probabilistic selection weighted by latency, success rate, and confidence. Routes more traffic to faster providers while still using slower ones.

Use Cases

  • Balance between performance and distribution
  • Avoiding single-provider concentration
  • Providers with significantly different performance profiles

Weight Formula

Each provider receives a weight calculated as:
weight = (1 / latency^beta) * success_rate * confidence * calls_scale
weight = max(weight, explore_floor)
Higher weights = higher selection probability.

Formula Components

Latency Term: 1 / latency^beta
  • Beta (default: 3.0) controls latency sensitivity
  • Higher beta = stronger preference for low latency
  • Floor (default: 30ms) prevents division by near-zero
Success Rate: success_rate * confidence
  • Filters unreliable providers
  • Confidence increases with call volume
Calls Scale: calls / min_calls
  • Reduces weight for providers with insufficient data
  • Minimum calls (default: 3)
Explore Floor: 0.05
  • Ensures all providers receive some traffic
  • Prevents complete starvation of slower providers

Implementation

weight_fn = fn ch ->
  case Map.get(metrics_map, key) do
    %{latency_ms: ms, success_rate: sr, total_calls: n, confidence_score: conf, last_updated_ms: updated} ->
      age_ms = current_time - updated
      if age_ms > freshness_cutoff do
        explore_floor  # Stale - minimum weight
      else
        calls_scale = if n >= min_calls, do: 1.0, else: n / max(min_calls, 1)
        denom = :erlang.max(ms, ms_floor)
        latency_term = 1.0 / :math.pow(denom, beta)
        sr_term = max(sr, min_sr)
        conf_term = conf
        max(explore_floor, latency_term * sr_term * conf_term * calls_scale)
      end
    _ ->
      # Missing data - use conservative weight
      baseline_latency = ctx.cold_start_baseline || 1000.0
      denom = :erlang.max(baseline_latency, ms_floor)
      latency_term = 1.0 / :math.pow(denom, beta)
      max(explore_floor, latency_term * 0.95 * 0.5)
  end
end

Enum.sort_by(channels, fn ch -> -(:rand.uniform() * weight_fn.(ch)) end)

Configuration

# Latency exponent (default: 3.0)
export LW_BETA=3.0

# Minimum latency denominator in ms (default: 30)
export LW_MS_FLOOR=30.0

# Minimum weight floor (default: 0.05)
export LW_EXPLORE_FLOOR=0.05

# Minimum calls for stable metrics (default: 3)
export LW_MIN_CALLS=3

# Minimum success rate (default: 0.85)
export LW_MIN_SR=0.85

Example

Method: eth_call
ProviderLatencySuccess RateCallsWeightSelection %
Alchemy200ms0.981000.0001260%
Infura350ms0.95800.0000230%
QuickNode500ms0.92500.0000110%
Result: Alchemy receives ~60% of traffic, but Infura and QuickNode still get meaningful load.

Priority

Module: Lasso.RPC.Strategies.Priority Selects providers in the order defined by the priority field in the profile. Lower priority values are tried first.

Use Cases

  • Preferred provider (e.g., your own node) with fallbacks
  • Predictable routing order
  • Explicit control over provider precedence

Configuration

Set priority in the profile YAML:
providers:
  - id: "my_node"
    url: "https://my-node.example.com"
    priority: 1
  - id: "alchemy_backup"
    url: "https://eth-mainnet.g.alchemy.com/v2/${ALCHEMY_API_KEY}"
    priority: 2
  - id: "public_fallback"
    url: "https://eth.llamarpc.com"
    priority: 3

Behavior

def rank_channels(channels, _method, _ctx, _profile, _chain) do
  Enum.sort_by(channels, & &1.config.priority)
end
Simple ascending sort by priority field. No dynamic reordering based on performance.

Example

Providers:
  • Provider A (priority: 1): Your node
  • Provider B (priority: 2): Alchemy
  • Provider C (priority: 3): Public RPC
Result: Always tries Provider A first, then B, then C, regardless of latency or performance.
Priority strategy still respects health tiering. A priority-1 provider with an open circuit will be excluded.

Health-Based Tiering

All strategies are subject to health-based tiering after strategy ranking. The tiering pipeline reorders providers into 4 tiers:

The 4 Tiers

  1. Tier 1: Closed circuit + not rate-limited (preferred)
  2. Tier 2: Closed circuit + rate-limited
  3. Tier 3: Half-open circuit + not rate-limited
  4. Tier 4: Half-open circuit + rate-limited
Excluded: Open circuit providers are filtered out entirely. Within each tier, the strategy’s original ranking is preserved.

Strategy-Health Interaction

StrategyWithin-Tier BehaviorCross-Tier Impact
Load BalancedRandom shuffleNo preference, healthy tiers dominate
FastestLatency-orderedFast provider may not receive traffic if unhealthy
Latency WeightedWeighted randomWeights apply only within each tier
PriorityPriority-orderedPriority applies only within each tier
Example: With fastest strategy, if the fastest provider has a half-open circuit, any closed-circuit provider will be tried first, even if it’s slower.

Per-Method Routing

Profiles support per-method strategy and provider overrides:
routing:
  default_strategy: "load_balanced"
  method_overrides:
    eth_getLogs:
      strategy: "fastest"  # Use fastest for log queries
    eth_call:
      providers: ["alchemy", "quicknode"]  # Restrict to specific providers
This allows fine-grained control for methods with different performance characteristics.

Provider Override

Bypass strategy selection entirely by routing directly to a specific provider:
POST /rpc/provider/:provider_id/:chain
The provider ID must match a provider configured in the profile. Health checks and circuit breakers still apply. Use cases:
  • Debugging specific provider behavior
  • Testing provider-specific features
  • Custom provider selection logic outside Lasso

Execution and Failover

After strategy ranking and tiering, Lasso attempts providers sequentially: Success: First provider that returns a valid RPC response (2xx status, valid JSON-RPC structure) Failure: Provider is skipped and the next provider in the list is tried. Failures increment circuit breaker counters. All Exhausted: Returns 503 Service Unavailable with details about which providers were tried and why they failed.

Configuration Summary

VariableStrategyDefaultDescription
FASTEST_MIN_CALLSFastest3Minimum calls for stable metrics
FASTEST_MIN_SUCCESS_RATEFastest0.9Minimum success rate filter
LW_BETALatency Weighted3.0Latency exponent
LW_MS_FLOORLatency Weighted30.0Minimum latency denominator (ms)
LW_EXPLORE_FLOORLatency Weighted0.05Minimum selection probability
LW_MIN_CALLSLatency Weighted3Minimum calls for stable metrics
LW_MIN_SRLatency Weighted0.85Minimum success rate

Next Steps

Provider Selection

Understand the 7-stage filter pipeline

Circuit Breakers

Learn about fault tolerance and automatic recovery

Profiles

Configure multi-tenant routing policies

Architecture

Explore the OTP supervision tree