Multiple Connections Per Peer
This example demonstrates how to use the multiple connections per peer feature in py-libp2p.
Overview
The multiple connections per peer feature allows a libp2p node to maintain multiple network connections to the same peer. This provides several benefits:
Improved reliability: If one connection fails, others remain available
Better performance: Load can be distributed across multiple connections
Enhanced throughput: Multiple streams can be created in parallel
Fault tolerance: Redundant connections provide backup paths
Configuration
The feature is configured through the ConnectionConfig class:
from libp2p.network.swarm import ConnectionConfig
# Default configuration
config = ConnectionConfig()
print(f"Max connections per peer: {config.max_connections_per_peer}")
print(f"Load balancing strategy: {config.load_balancing_strategy}")
# Custom configuration
custom_config = ConnectionConfig(
max_connections_per_peer=5,
connection_timeout=60.0,
load_balancing_strategy="least_loaded"
)
Load Balancing Strategies
Two load balancing strategies are available:
- Round Robin (default)
Cycles through connections in order, distributing load evenly.
- Least Loaded
Selects the connection with the fewest active streams.
API Usage
The new API provides direct access to multiple connections:
from libp2p import new_swarm
# Create swarm with multiple connections support
swarm = new_swarm()
# Dial a peer - returns list of connections
connections = await swarm.dial_peer(peer_id)
print(f"Established {len(connections)} connections")
# Get all connections to a peer
peer_connections = swarm.get_connections(peer_id)
# Get all connections (across all peers)
all_connections = swarm.get_connections()
# Get the complete connections map
connections_map = swarm.get_connections_map()
# Backward compatibility - get single connection
single_conn = swarm.get_connection(peer_id)
Backward Compatibility
Existing code continues to work through backward compatibility features:
# Legacy 1:1 mapping (returns first connection for each peer)
legacy_connections = swarm.connections_legacy
# Single connection access (returns first available connection)
conn = swarm.get_connection(peer_id)
Example
A complete working example is available in the examples/doc-examples/multiple_connections_example.py file.
Production Configuration
For production use, consider these settings:
RetryConfig Parameters
The RetryConfig class controls connection retry behavior with exponential backoff:
max_retries: Maximum number of retry attempts before giving up (default: 3)
initial_delay: Initial delay in seconds before the first retry (default: 0.1s)
max_delay: Maximum delay cap to prevent excessive wait times (default: 30.0s)
backoff_multiplier: Exponential backoff multiplier - each retry multiplies delay by this factor (default: 2.0)
jitter_factor: Random jitter (0.0-1.0) to prevent synchronized retries (default: 0.1)
ConnectionConfig Parameters
The ConnectionConfig class manages multi-connection behavior:
max_connections_per_peer: Maximum connections allowed to a single peer (default: 3)
connection_timeout: Timeout for establishing new connections in seconds (default: 30.0s)
load_balancing_strategy: Strategy for distributing streams (“round_robin” or “least_loaded”)
Load Balancing Strategies Explained
round_robin: Cycles through connections in order, distributing load evenly. Simple and predictable.
least_loaded: Selects the connection with the fewest active streams. Better for performance but more complex.
from libp2p.network.swarm import ConnectionConfig, RetryConfig
# Production-ready configuration
retry_config = RetryConfig(
max_retries=3, # Maximum retry attempts before giving up
initial_delay=0.1, # Start with 100ms delay
max_delay=30.0, # Cap exponential backoff at 30 seconds
backoff_multiplier=2.0, # Double delay each retry (100ms -> 200ms -> 400ms)
jitter_factor=0.1 # Add 10% random jitter to prevent thundering herd
)
connection_config = ConnectionConfig(
max_connections_per_peer=3, # Allow up to 3 connections per peer
connection_timeout=30.0, # 30 second timeout for new connections
load_balancing_strategy="round_robin" # Simple, predictable load distribution
)
swarm = new_swarm(
retry_config=retry_config,
connection_config=connection_config
)
How RetryConfig Works in Practice
With the configuration above, connection retries follow this pattern:
Attempt 1: Immediate connection attempt
Attempt 2: Wait 100ms ± 10ms jitter, then retry
Attempt 3: Wait 200ms ± 20ms jitter, then retry
Attempt 4: Wait 400ms ± 40ms jitter, then retry
Attempt 5: Wait 800ms ± 80ms jitter, then retry
Attempt 6: Wait 1.6s ± 160ms jitter, then retry
Attempt 7: Wait 3.2s ± 320ms jitter, then retry
Attempt 8: Wait 6.4s ± 640ms jitter, then retry
Attempt 9: Wait 12.8s ± 1.28s jitter, then retry
Attempt 10: Wait 25.6s ± 2.56s jitter, then retry
Attempt 11: Wait 30.0s (capped) ± 3.0s jitter, then retry
Attempt 12: Wait 30.0s (capped) ± 3.0s jitter, then retry
Give up: After 12 retries (3 initial + 9 retries), connection fails
The jitter prevents multiple clients from retrying simultaneously, reducing server load.
Parameter Tuning Guidelines
For Development/Testing: - Use lower max_retries (1-2) and shorter delays for faster feedback - Example: RetryConfig(max_retries=2, initial_delay=0.01, max_delay=0.1)
For Production: - Use moderate max_retries (3-5) with reasonable delays for reliability - Example: RetryConfig(max_retries=5, initial_delay=0.1, max_delay=60.0)
For High-Latency Networks: - Use higher max_retries (5-10) with longer delays - Example: RetryConfig(max_retries=8, initial_delay=0.5, max_delay=120.0)
For Load Balancing: - Use round_robin for simple, predictable behavior - Use least_loaded when you need optimal performance and can handle complexity
Architecture
The implementation follows the same architectural patterns as the Go and JavaScript reference implementations:
Core data structure: dict[ID, list[INetConn]] for 1:many mapping
API consistency: Methods like get_connections() match reference implementations
Load balancing: Integrated at the API level for optimal performance
Backward compatibility: Maintains existing interfaces for gradual migration
This design ensures consistency across libp2p implementations while providing the benefits of multiple connections per peer.