Skip to Content
FeaturesModulesgRPC ServerConnection Pooling

Connection Pooling

Django-CFG provides connection pooling for gRPC channels, reducing connection overhead and improving performance for high-traffic applications.

Why Connection Pooling?

Creating a new gRPC channel for each request is expensive:

OperationWithout PoolWith Pool
Connection setupEvery requestOnce per target
TLS handshakeEvery requestOnce per target
Channel warmupEvery requestOnce per target
Typical latency50-200ms1-5ms

Connection pooling reuses channels across requests, dramatically reducing latency.


Quick Start

from django_cfg.modules.django_grpc.services.client import ( get_channel_pool, AsyncResilientGRPCClient, ) # Option 1: Use global pool directly pool = get_channel_pool() async with pool.pooled_channel("localhost:50051") as channel: stub = MyServiceStub(channel) response = await stub.Method(request) # Option 2: Use resilient client with pooling async with AsyncResilientGRPCClient( host="localhost", port=50051, use_pool=True, ) as client: result = await client.call_method(...)

GRPCChannelPool (Async)

The async channel pool for grpc.aio.Channel.

Getting the Pool

from django_cfg.modules.django_grpc.services.client import ( get_channel_pool, PoolConfig, ) # Default configuration pool = get_channel_pool() # Custom configuration config = PoolConfig( max_size=50, idle_timeout=300.0, min_idle=5, ) pool = get_channel_pool(config)

Using pooled_channel Context Manager

The recommended way to use pooled channels:

async with pool.pooled_channel("localhost:50051") as channel: stub = MyServiceStub(channel) response = await stub.Method(request) # Channel automatically returned to pool

With TLS:

async with pool.pooled_channel( "secure-service:50051", use_tls=True, ) as channel: stub = SecureServiceStub(channel) response = await stub.Method(request)

Getting Channels Directly

For more control over channel lifecycle:

# Get channel (creates if needed) channel = await pool.get_channel("localhost:50051") # Use channel stub = MyServiceStub(channel) response = await stub.Method(request) # Channel remains in pool for reuse

Pool Statistics

stats = pool.get_stats() print(f"Total channels: {stats['total_channels']}") print(f"In use: {stats['channels_in_use']}") print(f"Idle: {stats['channels_idle']}") # Per-target stats for target, target_stats in stats['pools'].items(): print(f" {target}: {target_stats['in_use']}/{target_stats['total']}")

Closing the Pool

from django_cfg.modules.django_grpc.services.client import close_global_pool # Close all channels and shutdown pool await close_global_pool()

SyncGRPCChannelPool

For synchronous code, use SyncGRPCChannelPool:

from django_cfg.modules.django_grpc.services.client import ( get_sync_channel_pool, close_global_sync_pool, ) # Get sync pool pool = get_sync_channel_pool() # Get channel channel = pool.get_channel("localhost:50051") # Use channel stub = MyServiceStub(channel) response = stub.Method(request) # Close when done close_global_sync_pool()

Configuration

GrpcPoolConfig

The canonical pool configuration class. Import it from config.pool — it is the single source of truth, replacing older PoolConfig dataclass definitions.

from django_cfg.modules.django_grpc.config.pool import GrpcPoolConfig config = GrpcPoolConfig( enabled=True, max_size=20, idle_timeout=120.0, min_idle=2, max_age=3600.0, cleanup_interval=60.0, health_check_interval=30.0, channel_ready_timeout=5.0, )
FieldTypeDefaultDescription
enabledboolTrueEnable connection pooling
max_sizeint20Maximum channels in pool (1–200)
idle_timeoutfloat120.0Seconds before an idle channel is closed (10–3600)
min_idleint2Minimum idle channels to maintain per address (0–20)
max_agefloat3600.0Maximum channel lifetime in seconds (60–86400)
cleanup_intervalfloat60.0Seconds between pool cleanup cycles (10–600)
health_check_intervalfloat30.0Seconds between idle channel health checks (5–600)
channel_ready_timeoutfloat5.0Seconds to wait for a channel to become ready before giving up (1–60)

Integration with DjangoGrpcModuleConfig

from django_cfg.modules.django_grpc.__cfg__ import DjangoGrpcModuleConfig from django_cfg.modules.django_grpc.config.pool import GrpcPoolConfig grpc_module = DjangoGrpcModuleConfig( enabled=True, pool=GrpcPoolConfig( max_size=50, idle_timeout=300.0, health_check_interval=15.0, ), )

Integration with Clients

AsyncResilientGRPCClient

Enable pooling in the resilient client:

from django_cfg.modules.django_grpc.services.client import AsyncResilientGRPCClient async with AsyncResilientGRPCClient( host="localhost", port=50051, use_pool=True, # Enable pooling ) as client: # Channels are reused across calls result1 = await client.call_method(...) result2 = await client.call_method(...) # Check pool stats print(client.pool_stats)

Custom Pool Instance

Provide your own pool instance:

from django_cfg.modules.django_grpc.services.client import ( GRPCChannelPool, PoolConfig, AsyncResilientGRPCClient, ) # Create custom pool my_pool = GRPCChannelPool(PoolConfig(max_size=100)) # Use with client async with AsyncResilientGRPCClient( host="localhost", port=50051, use_pool=True, pool=my_pool, ) as client: result = await client.call_method(...) # Clean up custom pool await my_pool.close_all()

Monitoring

Pool Health Check

def check_pool_health(): pool = get_channel_pool() stats = pool.get_stats() # Check utilization utilization = stats['channels_in_use'] / max(stats['total_channels'], 1) if utilization > 0.9: print("WARNING: Pool near capacity") if pool.is_closed: print("ERROR: Pool is closed") return { "healthy": not pool.is_closed and utilization < 0.9, "utilization": utilization, "stats": stats, }

Metrics to Monitor

MetricDescriptionAlert Threshold
total_channelsChannels in poolNear max_size
channels_in_useActive channels> 90% of total
channels_idleAvailable channels< min_idle
pool.is_closedPool statusTrue

Performance Considerations

Pool Size Tuning

# Calculate based on concurrent requests # Rule of thumb: max_size = peak_concurrent_requests * 1.2 config = PoolConfig( max_size=int(expected_concurrent * 1.2), min_idle=int(expected_concurrent * 0.2), )

Idle Timeout

# Short timeout for bursty traffic config = PoolConfig(idle_timeout=60.0) # Long timeout for steady traffic config = PoolConfig(idle_timeout=300.0)

Channel Age

# Rotate channels periodically for load balancer compatibility config = PoolConfig(max_age=1800.0) # 30 minutes

Troubleshooting

Pool Exhaustion

Symptom: Slow channel acquisition, timeouts.

Cause: All channels in use.

Solution:

# Increase pool size config = PoolConfig(max_size=50) # Check for leaks (unreleased channels) stats = pool.get_stats() print(f"In use: {stats['channels_in_use']}")

Stale Channels

Symptom: Intermittent connection errors.

Cause: Channels kept too long.

Solution:

# Reduce max age config = PoolConfig(max_age=1800.0) # Reduce idle timeout config = PoolConfig(idle_timeout=60.0)

Memory Usage

Symptom: High memory consumption.

Cause: Too many idle channels.

Solution:

# Reduce pool size config = PoolConfig(max_size=10) # Reduce min idle config = PoolConfig(min_idle=1)

See Also

Last updated on