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.
16 KiB
Concurrent Request Optimization
Last updated: 2026-06-24 Status: GA Category: Performance & Scalability Type: reference Source: https://learn.microsoft.com/azure/foundry/openai/how-to/latency
Innhold
- Introduksjon
- Kjernekomponenter
- Concurrency Level Tuning
- Request Queueing Strategies
- Deadlock Prevention
- Resource Contention Resolution
- Norsk offentlig sektor
- Beslutningsrammeverk
- Referanser
- For Cosmo
Introduksjon
Concurrent request optimization handler om å maksimere antall samtidige forespørsler mot Azure OpenAI uten å overbelaste tjenesten eller miste forespørsler. Den optimale graden av samtidighet avhenger av deployment-type (Standard vs. PTU), tildelt kvote (TPM/RPM), modellens responstid og klientens evne til å håndtere parallelle forbindelser.
For Standard deployments bestemmer RPM-kvoten den harde grensen for samtidige forespørsler per minutt, men den faktiske grensen er ofte lavere fordi lange forespørsler blokkerer kapasitet. For PTU deployments er grensen definert av utilization — når prosessert kapasitet nærmer seg 100% av tildelte PTUs, begynner 429-feil. I begge tilfeller er nøkkelen å finne sweet spot der throughput er maksimert uten overdreven throttling.
For norsk offentlig sektor, der AI-applikasjoner kan betjene hundrevis av samtidige saksbehandlere, er concurrent request optimization avgjørende for å sikre jevn brukeropplevelse uten at noen opplever timeout eller feil.
Kjernekomponenter
| Komponent | Formål | Teknologi |
|---|---|---|
| Semaphore | Begrens concurrent requests klient-side | asyncio / SemaphoreSlim |
| Token Bucket | Rate limiting med burst-støtte | Custom / APIM |
| Connection Pool | Gjenbruk HTTP-forbindelser | HttpClient / aiohttp |
| Circuit Breaker | Forhindre kaskade ved overbelastning | Polly / custom |
| Queue | Buffer forespørsler under peak | Service Bus / in-memory |
Concurrency Level Tuning
Finn optimal concurrency
import asyncio
import time
from openai import AsyncAzureOpenAI, RateLimitError
async def find_optimal_concurrency(
client: AsyncAzureOpenAI,
model: str = "gpt-4o",
test_prompt: str = "Oppsummer dette kort.",
max_tokens: int = 200,
test_levels: list[int] = None,
requests_per_level: int = 50
) -> dict:
"""Find optimal concurrency level through progressive testing."""
if test_levels is None:
test_levels = [1, 5, 10, 20, 30, 50, 75, 100]
results = []
for concurrency in test_levels:
semaphore = asyncio.Semaphore(concurrency)
stats = {"success": 0, "throttled": 0, "errors": 0, "latencies": []}
async def send_one():
async with semaphore:
start = time.time()
try:
await client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": test_prompt}],
max_tokens=max_tokens
)
stats["latencies"].append(
(time.time() - start) * 1000)
stats["success"] += 1
except RateLimitError:
stats["throttled"] += 1
except Exception:
stats["errors"] += 1
start = time.time()
await asyncio.gather(
*[send_one() for _ in range(requests_per_level)])
duration = time.time() - start
total = stats["success"] + stats["throttled"] + stats["errors"]
throttle_rate = stats["throttled"] / max(total, 1) * 100
result = {
"concurrency": concurrency,
"throughput_rps": round(stats["success"] / duration, 2),
"throttle_rate_pct": round(throttle_rate, 1),
"p50_ms": round(sorted(stats["latencies"])[
len(stats["latencies"]) // 2], 0)
if stats["latencies"] else 0,
"p95_ms": round(sorted(stats["latencies"])[
int(len(stats["latencies"]) * 0.95)], 0)
if stats["latencies"] else 0,
"success": stats["success"],
"throttled": stats["throttled"]
}
results.append(result)
print(f"Concurrency {concurrency}: "
f"{result['throughput_rps']} RPS, "
f"{throttle_rate:.1f}% throttled, "
f"P50={result['p50_ms']}ms")
# Stopp hvis throttle rate er for høy
if throttle_rate > 30:
print(f"Stopping: throttle rate too high at {concurrency}")
break
# Finn optimal: best throughput med <5% throttling
acceptable = [r for r in results if r["throttle_rate_pct"] < 5]
if acceptable:
optimal = max(acceptable, key=lambda r: r["throughput_rps"])
else:
optimal = results[0]
return {
"optimal_concurrency": optimal["concurrency"],
"optimal_throughput_rps": optimal["throughput_rps"],
"all_results": results
}
Adaptive concurrency control
class AdaptiveConcurrencyController:
"""Dynamically adjust concurrency based on response signals."""
def __init__(
self,
initial_concurrency: int = 10,
min_concurrency: int = 1,
max_concurrency: int = 100,
increase_threshold: float = 0.02, # Øk hvis <2% throttled
decrease_threshold: float = 0.10, # Reduser hvis >10% throttled
adjustment_interval: float = 10.0 # Juster hvert 10. sekund
):
self.current = initial_concurrency
self.min_concurrency = min_concurrency
self.max_concurrency = max_concurrency
self.increase_threshold = increase_threshold
self.decrease_threshold = decrease_threshold
self.adjustment_interval = adjustment_interval
self._semaphore = asyncio.Semaphore(initial_concurrency)
self._window_success = 0
self._window_throttled = 0
self._last_adjustment = time.time()
async def acquire(self):
"""Acquire a concurrency slot."""
await self._semaphore.acquire()
def release(self, was_throttled: bool = False):
"""Release slot and record outcome."""
self._semaphore.release()
if was_throttled:
self._window_throttled += 1
else:
self._window_success += 1
self._maybe_adjust()
def _maybe_adjust(self):
"""Periodically adjust concurrency."""
now = time.time()
if now - self._last_adjustment < self.adjustment_interval:
return
total = self._window_success + self._window_throttled
if total < 10: # Ikke nok data
return
throttle_rate = self._window_throttled / total
old = self.current
if throttle_rate < self.increase_threshold:
# Trygt å øke
self.current = min(
self.current + max(1, self.current // 10),
self.max_concurrency)
elif throttle_rate > self.decrease_threshold:
# Må redusere
self.current = max(
self.current - max(1, self.current // 5),
self.min_concurrency)
if self.current != old:
# Opprett ny semaphore med justert limit
self._semaphore = asyncio.Semaphore(self.current)
print(f"Concurrency adjusted: {old} → {self.current} "
f"(throttle rate: {throttle_rate:.1%})")
self._window_success = 0
self._window_throttled = 0
self._last_adjustment = now
Request Queueing Strategies
Priority queue for AI-forespørsler
import asyncio
import heapq
from enum import IntEnum
from dataclasses import dataclass, field
from typing import Any
class Priority(IntEnum):
URGENT = 1
HIGH = 2
NORMAL = 3
LOW = 4
BACKGROUND = 5
@dataclass(order=True)
class PrioritizedRequest:
priority: int
timestamp: float = field(compare=True)
request: Any = field(compare=False)
future: asyncio.Future = field(compare=False, repr=False)
class PriorityRequestQueue:
"""Priority queue for AI requests with concurrency control."""
def __init__(self, max_concurrent: int = 20):
self._queue: list[PrioritizedRequest] = []
self._semaphore = asyncio.Semaphore(max_concurrent)
self._processing = True
async def submit(
self,
request: dict,
priority: Priority = Priority.NORMAL
) -> asyncio.Future:
"""Submit request with priority. Returns future."""
future = asyncio.get_event_loop().create_future()
item = PrioritizedRequest(
priority=priority.value,
timestamp=time.time(),
request=request,
future=future
)
heapq.heappush(self._queue, item)
return future
async def process_loop(self, process_fn):
"""Continuously process queued requests."""
while self._processing:
if not self._queue:
await asyncio.sleep(0.01)
continue
await self._semaphore.acquire()
item = heapq.heappop(self._queue)
asyncio.create_task(
self._process_item(item, process_fn))
async def _process_item(self, item, process_fn):
try:
result = await process_fn(item.request)
if not item.future.done():
item.future.set_result(result)
except Exception as e:
if not item.future.done():
item.future.set_exception(e)
finally:
self._semaphore.release()
Deadlock Prevention
Unngå resource starvation
class DeadlockPreventionWrapper:
"""Prevent deadlocks in concurrent AI request processing."""
def __init__(
self,
client: AsyncAzureOpenAI,
max_concurrent: int = 20,
request_timeout: float = 120.0,
starvation_timeout: float = 300.0
):
self.client = client
self.semaphore = asyncio.Semaphore(max_concurrent)
self.request_timeout = request_timeout
self.starvation_timeout = starvation_timeout
self._active_requests: dict[str, float] = {}
async def execute(self, request_id: str, **kwargs):
"""Execute with timeout and starvation protection."""
# Timeout på semaphore acquire — forhindrer deadlock
try:
await asyncio.wait_for(
self.semaphore.acquire(),
timeout=self.starvation_timeout
)
except asyncio.TimeoutError:
raise TimeoutError(
f"Request {request_id} starved waiting for "
f"concurrency slot for {self.starvation_timeout}s. "
f"Consider increasing max_concurrent or reducing "
f"request volume.")
self._active_requests[request_id] = time.time()
try:
# Timeout på selve forespørselen
result = await asyncio.wait_for(
self.client.chat.completions.create(**kwargs),
timeout=self.request_timeout
)
return result
except asyncio.TimeoutError:
raise TimeoutError(
f"Request {request_id} timed out after "
f"{self.request_timeout}s")
finally:
self._active_requests.pop(request_id, None)
self.semaphore.release()
@property
def active_count(self) -> int:
return len(self._active_requests)
def get_stuck_requests(self, threshold_seconds: float = 60) -> list:
"""Identify requests that may be stuck."""
now = time.time()
return [
{"id": rid, "age_seconds": round(now - start, 1)}
for rid, start in self._active_requests.items()
if now - start > threshold_seconds
]
Resource Contention Resolution
Token bucket for fair scheduling
import time
class TokenBucket:
"""Token bucket rate limiter for fair resource sharing."""
def __init__(
self,
tokens_per_second: float,
max_burst: int = 10
):
self.rate = tokens_per_second
self.max_burst = max_burst
self._tokens = max_burst
self._last_refill = time.time()
self._lock = asyncio.Lock()
async def acquire(self, tokens: int = 1) -> float:
"""Acquire tokens, waiting if necessary. Returns wait time."""
async with self._lock:
self._refill()
if self._tokens >= tokens:
self._tokens -= tokens
return 0
# Beregn ventetid
deficit = tokens - self._tokens
wait_time = deficit / self.rate
await asyncio.sleep(wait_time)
self._refill()
self._tokens -= tokens
return wait_time
def _refill(self):
now = time.time()
elapsed = now - self._last_refill
self._tokens = min(
self.max_burst,
self._tokens + elapsed * self.rate
)
self._last_refill = now
class FairScheduler:
"""Fair scheduling across multiple tenants/users."""
def __init__(self, total_rps: float, num_tenants: int):
self.total_rps = total_rps
per_tenant_rps = total_rps / num_tenants
self.buckets: dict[str, TokenBucket] = {}
self._default_rps = per_tenant_rps
def get_bucket(self, tenant_id: str) -> TokenBucket:
if tenant_id not in self.buckets:
self.buckets[tenant_id] = TokenBucket(
tokens_per_second=self._default_rps,
max_burst=int(self._default_rps * 2)
)
return self.buckets[tenant_id]
Norsk offentlig sektor
- Fair use: I multi-tenant løsninger der flere enheter deler samme Azure OpenAI-deployment, bruk per-tenant rate limiting for å sikre rettferdig fordeling.
- Brukeropplevelse: Sett starvation timeout til maks ventetid brukere aksepterer (typisk 30-60 sekunder). Returner informativ feilmelding ved timeout.
- Overvåking: Logg concurrent request-nivå, kø-dybde og starvation-hendelser i Application Insights for kapasitetsplanlegging.
- Skalering: Planlegg for peak-perioder (morgen 08-10, etter lunsj 12-13) med høyere concurrent limits eller ekstra kvote.
Beslutningsrammeverk
| Scenario | Anbefaling | Begrunnelse |
|---|---|---|
| Ukjent workload | Start med 10 concurrent, juster | Unngå initial throttling |
| Forutsigbar, jevn trafikk | Statisk semaphore på optimal nivå | Enklest å implementere |
| Variable peaks | Adaptive concurrency controller | Automatisk tilpasning |
| Multi-tenant | Priority queue + per-tenant bucket | Fair resource sharing |
| Kritisk latens | Lav concurrency + PTU | Forutsigbar responstid |
Referanser
- Manage Azure OpenAI quota — RPM/TPM grenser
- Performance and latency — Concurrent requests og throughput
- Provisioned throughput — PTU utilization
For Cosmo
- Bruk denne referansen når kunden opplever timeout, starvation eller ujevn ytelse i AI-applikasjoner med mange samtidige brukere.
- Start konservativt (10-20 concurrent) og øk gradvis mens du monitorerer throttle rate — aldri gå rett til 100 concurrent.
- Adaptive concurrency control er anbefalt for produksjon — statiske verdier fungerer dårlig når trafikkmønstre endres.
- Prioritetskøer er viktige for multi-tenant: sørg for at kritiske oppgaver (saksbehandler-beslutninger) ikke blokkeres av bakgrunnsjobber.
- Deadlock prevention med timeouts er obligatorisk — uten det kan en hengende forespørsel blokkere alle slots permanent.