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:

  1. Attempt 1: Immediate connection attempt

  2. Attempt 2: Wait 100ms ± 10ms jitter, then retry

  3. Attempt 3: Wait 200ms ± 20ms jitter, then retry

  4. Attempt 4: Wait 400ms ± 40ms jitter, then retry

  5. Attempt 5: Wait 800ms ± 80ms jitter, then retry

  6. Attempt 6: Wait 1.6s ± 160ms jitter, then retry

  7. Attempt 7: Wait 3.2s ± 320ms jitter, then retry

  8. Attempt 8: Wait 6.4s ± 640ms jitter, then retry

  9. Attempt 9: Wait 12.8s ± 1.28s jitter, then retry

  10. Attempt 10: Wait 25.6s ± 2.56s jitter, then retry

  11. Attempt 11: Wait 30.0s (capped) ± 3.0s jitter, then retry

  12. Attempt 12: Wait 30.0s (capped) ± 3.0s jitter, then retry

  13. 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.