# Connection Pooling Patterns

**Last updated:** 2026-04 | Verified: MCP 2026-04
**Status:** GA
**Category:** Performance & Scalability

---

## Introduksjon

Connection pooling er en kritisk ytelsesoptimalisering for applikasjoner som kommuniserer med Azure AI Services. Hver HTTP-forbindelse til Azure OpenAI eller andre AI-endepunkter krever TCP-håndtrykk og eventuelt TLS-forhandling, noe som legger til betydelig latens per forespørsel. Uten connection pooling opprettes og lukkes forbindelser for hver eneste forespørsel, noe som fører til port-utmattelse, økt responstid og unødvendig CPU-bruk.

I .NET-økosystemet er `HttpClient` den sentrale klassen for HTTP-kommunikasjon, og Microsofts offisielle retningslinjer er tydelige: bruk én statisk `HttpClient`-instans per logisk klient, eller bruk `IHttpClientFactory` for å håndtere DNS-endringer og connection lifecycle. Azure OpenAI SDK-ene (både Python og C#) håndterer connection pooling internt, men korrekt konfigurasjon er fortsatt nødvendig for optimal ytelse.

For norsk offentlig sektor der AI-tjenester typisk nås via private endpoints og ofte går gjennom Azure API Management, er connection pooling spesielt viktig. Nettverkskjeden (klient → APIM → Private Endpoint → Azure OpenAI) multipliserer latens-kostnaden av nye forbindelser, og korrekt pooling kan redusere p50-latens med 30-50 ms per forespørsel.

## Kjernekomponenter

| Komponent | Formål | Teknologi |
|-----------|--------|-----------|
| HttpClient / HttpClientFactory | HTTP connection management i .NET | System.Net.Http |
| SocketsHttpHandler | Underliggende socket-håndtering med pool | .NET 6+ |
| aiohttp.ClientSession | Asynkron HTTP med connection pool i Python | aiohttp |
| httpx.AsyncClient | Moderne asynkron HTTP-klient med pool | httpx |
| Azure OpenAI SDK | Innebygd connection management | azure-ai-openai |
| Azure API Management | Gateway med backend connection pooling | Azure APIM |

## Pool Sizing-strategier

### Beregning av optimal pool-størrelse

Pool-størrelsen bør baseres på forventet concurrent request-volum og responstid fra backend-tjenesten.

```python
# Beregn optimal pool-størrelse
# Formel: pool_size = concurrent_requests * avg_response_time_seconds / target_utilization

def calculate_pool_size(
    concurrent_users: int,
    avg_response_time_ms: float,
    requests_per_user_per_second: float = 1.0,
    target_utilization: float = 0.75
) -> int:
    """Calculate optimal connection pool size for AI workloads."""
    concurrent_requests = concurrent_users * requests_per_user_per_second
    avg_response_time_s = avg_response_time_ms / 1000

    # Connections needed = concurrent requests * time each holds a connection
    connections_needed = concurrent_requests * avg_response_time_s

    # Add headroom for bursts
    pool_size = int(connections_needed / target_utilization)

    # Azure OpenAI typical ranges
    return max(pool_size, 10)  # Minimum 10 connections

# Eksempel: 50 samtidige brukere, 800ms avg responstid
optimal = calculate_pool_size(
    concurrent_users=50,
    avg_response_time_ms=800,
    requests_per_user_per_second=0.5
)
print(f"Anbefalt pool-størrelse: {optimal}")  # ~27 connections
```

### .NET HttpClientFactory-konfigurasjon

```csharp
// Program.cs - Optimal HttpClient-konfigurasjon for Azure OpenAI
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Http;

var builder = WebApplication.CreateBuilder(args);

// Registrer named HttpClient for Azure OpenAI
builder.Services.AddHttpClient("AzureOpenAI", client =>
{
    client.BaseAddress = new Uri(
        builder.Configuration["AzureOpenAI:Endpoint"]!);
    client.DefaultRequestHeaders.Add("api-key",
        builder.Configuration["AzureOpenAI:ApiKey"]!);
    client.Timeout = TimeSpan.FromSeconds(120);
})
.ConfigurePrimaryHttpMessageHandler(() => new SocketsHttpHandler
{
    // Pool-konfigurasjon
    MaxConnectionsPerServer = 50,          // Maks connections per host
    PooledConnectionLifetime = TimeSpan.FromMinutes(5),  // DNS refresh
    PooledConnectionIdleTimeout = TimeSpan.FromMinutes(2),

    // Keep-alive
    KeepAlivePingPolicy = HttpKeepAlivePingPolicy.WithActiveRequests,
    KeepAlivePingDelay = TimeSpan.FromSeconds(30),
    KeepAlivePingTimeout = TimeSpan.FromSeconds(10),

    // Performance
    EnableMultipleHttp2Connections = true,
    AutomaticDecompression = System.Net.DecompressionMethods.GZip
})
.SetHandlerLifetime(TimeSpan.FromMinutes(10));  // Handler rotation

// Registrer Azure OpenAI-klient med pooled HttpClient
builder.Services.AddSingleton(sp =>
{
    var httpClientFactory = sp.GetRequiredService<IHttpClientFactory>();
    var httpClient = httpClientFactory.CreateClient("AzureOpenAI");

    return new Azure.AI.OpenAI.AzureOpenAIClient(
        new Uri(builder.Configuration["AzureOpenAI:Endpoint"]!),
        new Azure.AzureKeyCredential(
            builder.Configuration["AzureOpenAI:ApiKey"]!));
});
```

## Keep-alive-konfigurasjon

### HTTP/2 Multiplexing

Azure OpenAI støtter HTTP/2, som muliggjør multipleksing av flere forespørsler over én enkelt TCP-forbindelse:

```csharp
// HTTP/2 multiplexing for Azure OpenAI
var handler = new SocketsHttpHandler
{
    // Aktiver HTTP/2 med multipleksing
    EnableMultipleHttp2Connections = true,

    // Keep-alive for langvarige streams
    KeepAlivePingPolicy = HttpKeepAlivePingPolicy.Always,
    KeepAlivePingDelay = TimeSpan.FromSeconds(15),
    KeepAlivePingTimeout = TimeSpan.FromSeconds(5),

    // Connection lifecycle
    PooledConnectionLifetime = TimeSpan.FromMinutes(10),
    PooledConnectionIdleTimeout = TimeSpan.FromMinutes(2),
    MaxConnectionsPerServer = 100
};

var client = new HttpClient(handler)
{
    DefaultRequestVersion = HttpVersion.Version20,
    DefaultVersionPolicy = HttpVersionPolicy.RequestVersionOrLower
};
```

### Python aiohttp Session Management

```python
import aiohttp
import asyncio
from openai import AsyncAzureOpenAI

async def create_optimized_session() -> aiohttp.ClientSession:
    """Create an optimized aiohttp session for Azure OpenAI."""
    connector = aiohttp.TCPConnector(
        limit=100,              # Total connection pool size
        limit_per_host=50,      # Max connections per host
        ttl_dns_cache=300,      # DNS cache TTL i sekunder
        keepalive_timeout=30,   # Keep-alive timeout
        enable_cleanup_closed=True,
        force_close=False       # Gjenbruk connections
    )

    timeout = aiohttp.ClientTimeout(
        total=120,              # Total timeout
        connect=10,             # Connection timeout
        sock_read=60            # Read timeout
    )

    return aiohttp.ClientSession(
        connector=connector,
        timeout=timeout,
        headers={"Connection": "keep-alive"}
    )

# Bruk med Azure OpenAI Python SDK
async def create_pooled_openai_client() -> AsyncAzureOpenAI:
    """Create Azure OpenAI client with optimized connection pooling."""
    import httpx

    transport = httpx.AsyncHTTPTransport(
        retries=3,
        limits=httpx.Limits(
            max_connections=100,
            max_keepalive_connections=50,
            keepalive_expiry=30
        )
    )

    http_client = httpx.AsyncClient(
        transport=transport,
        timeout=httpx.Timeout(120.0, connect=10.0)
    )

    return AsyncAzureOpenAI(
        azure_endpoint="https://my-aoai.openai.azure.com",
        api_key="...",
        api_version="2024-10-21",
        http_client=http_client
    )
```

## Connection Recycling

### Håndtering av DNS-endringer

Når Azure OpenAI-endepunkter er bak Traffic Manager eller Azure Front Door, endres IP-adresser regelmessig. Connection pooling må balansere gjenbruk med DNS-fornyelse:

```csharp
// Connection recycling pattern
public class ConnectionRecyclingConfig
{
    // PooledConnectionLifetime: Tvinger nye DNS-oppslag
    // Sett lavere enn DNS TTL for failover-scenarier
    public TimeSpan PooledConnectionLifetime { get; set; }
        = TimeSpan.FromMinutes(5);

    // PooledConnectionIdleTimeout: Fjern ubrukte connections
    public TimeSpan PooledConnectionIdleTimeout { get; set; }
        = TimeSpan.FromMinutes(2);

    // Handler lifetime for IHttpClientFactory
    // Roterer hele handleren — nye connections med ny DNS
    public TimeSpan HandlerLifetime { get; set; }
        = TimeSpan.FromMinutes(10);
}

// Anti-pattern: ALDRI gjør dette
// ❌ var client = new HttpClient();  // Ny for hver forespørsel
// ❌ using var client = new HttpClient(); // Disponeres for tidlig

// Korrekt pattern: Singleton eller factory
// ✅ Statisk HttpClient med SocketsHttpHandler
// ✅ IHttpClientFactory i DI
```

## Load Distribution

### Round-robin over multiple Azure OpenAI-instanser

```python
import asyncio
import random
from dataclasses import dataclass, field
from typing import Optional
from openai import AsyncAzureOpenAI

@dataclass
class AzureOpenAIBackend:
    endpoint: str
    api_key: str
    priority: int = 1
    is_healthy: bool = True
    retry_after: Optional[float] = None
    client: Optional[AsyncAzureOpenAI] = field(default=None, repr=False)

class ConnectionPoolLoadBalancer:
    """Load balancer with dedicated connection pools per backend."""

    def __init__(self, backends: list[AzureOpenAIBackend]):
        self.backends = backends
        # Separat connection pool per backend
        for backend in self.backends:
            backend.client = AsyncAzureOpenAI(
                azure_endpoint=backend.endpoint,
                api_key=backend.api_key,
                api_version="2024-10-21",
                max_retries=0  # Vi håndterer retry selv
            )

    def _select_backend(self) -> AzureOpenAIBackend:
        """Select backend by priority, then random among same priority."""
        import time

        # Filtrer friske backends
        available = [
            b for b in self.backends
            if b.is_healthy or (
                b.retry_after and time.time() > b.retry_after
            )
        ]

        if not available:
            available = self.backends  # Fallback til alle

        # Velg laveste prioritet (høyest prioritet)
        min_priority = min(b.priority for b in available)
        candidates = [b for b in available if b.priority == min_priority]

        return random.choice(candidates)

    async def chat_completion(self, messages: list, **kwargs):
        """Route request to best available backend."""
        import time

        for attempt in range(len(self.backends)):
            backend = self._select_backend()
            try:
                response = await backend.client.chat.completions.create(
                    messages=messages, **kwargs
                )
                backend.is_healthy = True
                return response
            except Exception as e:
                if hasattr(e, 'status_code') and e.status_code == 429:
                    retry_after = getattr(e, 'retry_after', 10)
                    backend.retry_after = time.time() + retry_after
                    backend.is_healthy = False
                elif hasattr(e, 'status_code') and e.status_code >= 500:
                    backend.is_healthy = False
                else:
                    raise

        raise Exception("All backends exhausted")
```


## Azure API Management som Connection Pooling-lag (oppdatert 2026-04)

APIM håndterer backend connection pooling mot Azure OpenAI, noe som avlaster klientsiden:

### APIM Backend Pool-konfigurasjon

```xml
<!-- APIM: Backend pool med automatisk connection management -->
<policies>
    <inbound>
        <base />
        <!-- APIM gjenbruker backend-connections automatisk via intern pool -->
        <!-- Klientene ser APIM som et enkelt endepunkt -->
        <set-backend-service id="aoai-pool" backend-id="aoai-norway-backend" />
        
        <!-- Legg til correlation ID for tracing -->
        <set-header name="x-correlation-id" exists-action="skip">
            <value>@(context.RequestId)</value>
        </set-header>
    </inbound>
    
    <backend>
        <retry condition="@(context.Response.StatusCode == 429 || context.Response.StatusCode >= 500)"
               count="3" interval="0" first-fast-retry="true">
            <!-- APIM håndterer retry mot backend-pool -->
            <forward-request timeout="120" />
        </retry>
    </backend>
    
    <outbound>
        <!-- Eksponer backend latens-metrikk til klient -->
        <set-header name="x-backend-latency-ms" exists-action="override">
            <value>@(context.Elapsed.TotalMilliseconds.ToString())</value>
        </set-header>
    </outbound>
</policies>
```

### Backend-konfigurasjoner i APIM (4 topologier)

Microsoft anbefaler disse mønstrene for APIM connection pooling mot Azure OpenAI:

1. **Single backend**: Én APIM → én Azure OpenAI (enkelt, begrenset kvote)
2. **Multi-backend single region**: APIM med weighted round-robin mellom Azure OpenAI-instanser
3. **Multi-subscription**: Separate Azure OpenAI-instanser i ulike subscriptions for kvote-skalering
4. **Multi-region**: APIM i flere regioner, each med regionale backends

Klientene trenger aldri kjenne til antallet backends — APIM håndterer routing transparent.

## Norsk offentlig sektor

Connection pooling har spesielle hensyn for norsk offentlig sektor:

- **Data residency**: Alle connections må gå via regioner som oppfyller Schrems II-kravene. Ved bruk av Azure Norway East som primær region, konfigurer `PooledConnectionLifetime` kort nok til å håndtere failover til Sweden Central.
- **Private endpoints**: Connection pools som bruker Private Link har andre DNS-oppløsningsmønstre. Konfigurer `ttl_dns_cache` i Python eller `PooledConnectionLifetime` i .NET til å matche DNS TTL for privatelink-soner (typisk 10 sekunder).
- **NSMs grunnprinsipper**: Logging av connection pool-metrikker (aktive connections, pool hits/misses, connection errors) er påkrevd for å oppfylle krav om overvåking av nettverkstrafikk.
- **Anskaffelsesreglement**: Ved bruk av tredjepartsbiblioteker for connection pooling, verifiser at de er godkjent for bruk i offentlig sektor (åpen kildekode med akseptabel lisens).

## Beslutningsrammeverk

| Scenario | Anbefaling | Begrunnelse |
|----------|------------|-------------|
| Enkelttjeneste, lav trafikk (<10 RPS) | Statisk HttpClient med default pool | Enkel oppsett, tilstrekkelig ytelse |
| Enkelttjeneste, høy trafikk (>50 RPS) | HttpClientFactory med SocketsHttpHandler | DNS rotation, pool sizing, monitoring |
| Multi-region med failover | Separat pool per region + load balancer | Isolerer feil, støtter weighted routing |
| Via Azure APIM | APIM backend pool + klient-side pool | APIM håndterer backend-balansering |
| Streaming/SSE-respons | Dedikert pool med lange timeouts | Streaming holder connections åpne lenger |
| Azure Functions (serverless) | Static HttpClient i startup | Unngå cold start connection overhead |

## Referanser

- [Guidelines for using HttpClient](https://learn.microsoft.com/dotnet/fundamentals/networking/http/httpclient-guidelines) — HttpClient best practices
- [Pool HTTP connections with HttpClientFactory](https://learn.microsoft.com/aspnet/core/performance/performance-best-practices) — ASP.NET performance
- [Manage connections in Azure Functions](https://learn.microsoft.com/azure/azure-functions/manage-connections) — Serverless connection management
- [Use a gateway in front of multiple Azure OpenAI deployments or instances](https://learn.microsoft.com/azure/architecture/ai-ml/guide/azure-openai-gateway-multi-backend) — Multi-backend gateway patterns (Azure OpenAI i Foundry Models) — Verified (MCP 2026-04)

## For Cosmo

- **Bruk denne referansen** når kunden rapporterer høy latens, port-utmattelse, eller timeout-feil mot Azure OpenAI — connection pooling er ofte root cause.
- Anbefal `IHttpClientFactory` for .NET og `httpx.AsyncClient` med `Limits` for Python — aldri instansier `HttpClient` per forespørsel.
- For multi-region AI-arkitekturer, opprett separate connection pools per region med individuelle health checks og retry-after tracking.
- Sett `PooledConnectionLifetime` til 2-5 minutter for å balansere DNS-fornyelse med connection gjenbruk — kortere for failover-scenarier.
- Monitorer alltid `connections.active`, `connections.idle` og `pool.exhausted` metrikker i Application Insights for å oppdage pool-problemer tidlig.
