Ordre 20260912T193441Z-7358817909. Steg 1 var ikke transformen, men å rette
roadmapens R13-gate og få den ratifisert. Gaten `grep -rl "Cosmo"
skills/*/references -> 0` var usann på to uavhengige måter:
1. Ordren fanget den første: 451 av forekomstene er Azure Cosmos DB, ekte
produktinnhold. Diskriminatoren er ikke bokstaven «s» — `Cosmos <norsk
substantiv>` er genitiv av personaen (`### Cosmos tonalitet`), mens
`Cosmos DB`/`CosmosClient`/`cosmos_ru` er produkt.
2. Denne økten fant den andre: 132 persona-forekomster ligger i prosa,
tabeller, dialog-replikker og proveniens-linjer. Heading-nøytralisering
kan ikke nå dem, så «0 persona» er uoppnåelig også under den ratifiserte
formen. Operatøren ratifiserte alternativ A: gaten speiler formen, og de
132 bokføres til R13b/R14.
Tre korreksjoner av premisser som sto i ordren og STATE:
«ca 320 produkt» -> 451 (case-sensitivt nett manglet 327 lowercase
TOC-ankre + 99 identifikatorer; sann nevner 1 638)
«169 headinger» -> 401. 169 var `^## For Cosmo`-prefikset (168) og var
internt inkonsistent med sin egen topp-variant (204)
«417 matcher ingen
populasjon» -> 417 er cosmo-headinger utenfor kodefences; briefens
nevner var reell hele tiden
Fence-bevissthet er målt skadelig, ikke nødvendig: begge toggle-regler er
gale på dette korpuset (naiv toggle skjuler en ekte heading i
chain-of-thought-prompting.md, CommonMark-regelen ubalanserer
service-level-documentation-dr.md). Fence-agnostisk deteksjon finner 401
heading-linjer i nøyaktig de samme 40 variantene som fence-bevisst finner
400 i — ingen kodeblokk-linje er byte-identisk til en persona-heading. Derfor
nøkles transformen på 40 enumererte heading-tekster og ignorerer fences. En
ukjent variant kaster; en slug-kollisjon kaster. Ingenting auto-fikses.
TOC-en regenereres ikke, den rettes kirurgisk: alle 327 persona-lenker hadde
lenketekst lik én av de 40 heading-tekstene og anker lik slugify av den
(327/327, 0 avvik), så heading og TOC-entry skrives i samme operasjon og
ingen mellomtilstand etterlater en død lenke.
Ratifisert målform: `For Cosmo`, `For Cosmo Skyberg` og `For arkitekten
(Cosmo)` konvergerer på `For arkitekten`. To filer kolliderte og er adjudisert
ved å lese dem, ikke ved regel.
Verifisering (alle 7 kriterier fra ordren):
G1 persona på heading-linjer 401 -> 0
G2 døde fragmentlenker 1 -> 1 (pre-eksisterende, unntatt)
G3 produkt-forekomster 451 -> 451; `Cosmos DB|Azure Cosmos` 308 = 308
de 3 kun-produkt-filene byte-identiske
nettet validert begge veier injisert persona feller G1; genitiv feller G1;
produkt-heading og de 3 filene passerer
hele diffen 802 heading-linjer + 654 TOC-linjer, ANNET = 0
linjeantall 728 lagt til = 728 slettet
suite 1120/1120 (1097 + 23 nye)
validate-plugin 250 PASS / 0 FAIL
stikkprøve 10 filer, alle 5 skills, inkl. de 3 mest
produkt-tunge (26/20/19) — kun heading+TOC
Utenfor scope, urørt: de 4 SKILL.md, de 23 commands, CLAUDE.md, README.md,
NOTICE.md, docs/ (alt R14).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
17 KiB
Connection Pooling Patterns
Last updated: 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
- Kjernekomponenter
- Pool Sizing-strategier
- Keep-alive-konfigurasjon
- Connection Recycling
- Load Distribution
- Azure API Management som Connection Pooling-lag (oppdatert 2026-06-19)
- Norsk offentlig sektor
- Beslutningsrammeverk
- Referanser
- For arkitekten
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.
# 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
// 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:
// 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
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:
// 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
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
<!-- 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:
- Single backend: Én APIM → én Azure OpenAI (enkelt, begrenset kvote)
- Multi-backend single region: APIM med weighted round-robin mellom Azure OpenAI-instanser
- Multi-subscription: Separate Azure OpenAI-instanser i ulike subscriptions for kvote-skalering
- 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
PooledConnectionLifetimekort 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_cachei Python ellerPooledConnectionLifetimei .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 — HttpClient best practices
- Pool HTTP connections with HttpClientFactory — ASP.NET performance
- Manage connections in Azure Functions — Serverless connection management
- Use a gateway in front of multiple Azure OpenAI deployments or instances — Multi-backend gateway patterns (Azure OpenAI i Foundry Models) — Verified (MCP 2026-06-19)
For arkitekten
- 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
IHttpClientFactoryfor .NET oghttpx.AsyncClientmedLimitsfor Python — aldri instansierHttpClientper forespørsel. - For multi-region AI-arkitekturer, opprett separate connection pools per region med individuelle health checks og retry-after tracking.
- Sett
PooledConnectionLifetimetil 2-5 minutter for å balansere DNS-fornyelse med connection gjenbruk — kortere for failover-scenarier. - Monitorer alltid
connections.active,connections.idleogpool.exhaustedmetrikker i Application Insights for å oppdage pool-problemer tidlig.