Steg 9 (R4): unified migrate-corpus.mjs --write over engineering/governance/ infrastructure/security. 327 filer mutert, verified=null, prosa byte-identisk (fra første ## seksjon), advisor urørt (0 endringer). To applier-fixes oppdaget under kjøring (TDD, RED→GREEN): - insertHeaderFields: anker faller nå tilbake når en meta-linje selv passerer 500B (2 filer pakket et avsnitt i **Status:** → Type/Source landet utenfor scan-vinduet, applierens post-write-assertion fanget + restaurerte). - normalizeStaleVerified: fjerner nå ALLE stale non-date **Verified:** i 500B-vinduet, inkl. stray body-dup rett under --- (9 mlops-genaiops-filer var ellers falskt "verified"/fresh, droppet fra worklist). Operatør-godkjent utvidelse av carve-out; kun stray metadata-linjer, aldri prosa. test-transform-criterion: precondition oppdatert til post-migrasjons-sannhet (fila bærer nå Source). Suite 728/728 grønn.
416 lines
17 KiB
Markdown
416 lines
17 KiB
Markdown
# Connection Pooling Patterns
|
|
|
|
**Last updated:** 2026-06-19 | Verified: MCP 2026-06-19
|
|
**Status:** GA
|
|
**Category:** Performance & Scalability
|
|
**Type:** reference
|
|
**Source:** https://learn.microsoft.com/azure/architecture/ai-ml/guide/azure-openai-gateway-multi-backend
|
|
|
|
---
|
|
|
|
## Innhold
|
|
|
|
- [Introduksjon](#introduksjon)
|
|
- [Kjernekomponenter](#kjernekomponenter)
|
|
- [Pool Sizing-strategier](#pool-sizing-strategier)
|
|
- [Keep-alive-konfigurasjon](#keep-alive-konfigurasjon)
|
|
- [Connection Recycling](#connection-recycling)
|
|
- [Load Distribution](#load-distribution)
|
|
- [Azure API Management som Connection Pooling-lag (oppdatert 2026-06-19)](#azure-api-management-som-connection-pooling-lag-oppdatert-2026-06-19)
|
|
- [Norsk offentlig sektor](#norsk-offentlig-sektor)
|
|
- [Beslutningsrammeverk](#beslutningsrammeverk)
|
|
- [Referanser](#referanser)
|
|
- [For Cosmo](#for-cosmo)
|
|
|
|
## 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-06-19)
|
|
|
|
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-06-19)
|
|
|
|
## 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.
|