ms-ai-architect/skills/ms-ai-security/references/performance-scalability/concurrent-request-optimization.md
Kjell Tore Guttormsen dd1036ab8a fix(ms-ai-architect): Foundry URL-navnerom-migrering (ai-foundry → foundry/foundry-classic, 141 filer)
Task #5 del 1/3 (URL-migrering). Verifiseringen motbeviste STATE.md-premisset om ren prefix-swap: rebrand er per-URL, ikke mekanisk. En blind sed ai-foundry→foundry ville lagd 56 nye 404-er (classic-stiene finnes ikke under nytt foundry/-prefiks — bekreftet empirisk).

Metode: resolverte alle 237 unike KB-URLer mot live redirects (curl -L), bygde full-URL→full-URL-mapping fra faktisk url_effective. Bevarer locale-form, query (?view=) og #fragment per lenke.

- 231 navnerom-erstatninger over 141 filer (408 forekomster):
  - 161 → azure/foundry/ (98 ren prefix-swap + 10 sti-reorg + reorg-tilfeller)
  - 69 → azure/foundry-classic/ (eldre hub-spor: assistants, hub-DR, on-your-data; faktisk redirect-mål per operatorvalg)
  - 1 → azure/foundry-local/

- 2 døde lenker (404) fikset til verifiserte mål:
  - agent-service → azure/foundry/agents/overview
  - concepts/evaluation-evaluators/ → azure/foundry/how-to/evaluate-generative-ai-app

- 5 path-/display-referanser (uten https://, i backticks/lenketekst) rettet manuelt.

- 6 slug-baserte ai-foundry-treff urørt (scope-grense): managed-grafana-dashboard, security-baseline, power-platform prompt-builder, architecture baseline-chat (sistnevnte slug-rebrand i annet navnerom — mulig fremtidig funn).

- Parkert til task #5 del 2/3: Norway East GPT-5-datasuverenitet-fiks + modellkatalog-utvidelse (5.3/5.4/5.5, gpt-oss, sora-2).

Verifisert: 0 gjenværende azure/ai-foundry/-navnerom i skills/. validate-plugin.sh 219 PASS. test-kb-integrity.sh 117/117 passed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Claude-Session: https://claude.ai/code/session_01REiKFhP4w6xGXXqWKpPCJJ
2026-06-18 13:37:06 +02:00

431 lines
15 KiB
Markdown

# Concurrent Request Optimization
**Last updated:** 2026-02
**Status:** GA
**Category:** Performance & Scalability
---
## 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
```python
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
```python
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
```python
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
```python
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
```python
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](https://learn.microsoft.com/azure/foundry-classic/openai/how-to/quota) — RPM/TPM grenser
- [Performance and latency](https://learn.microsoft.com/azure/foundry/openai/how-to/latency) — Concurrent requests og throughput
- [Provisioned throughput](https://learn.microsoft.com/azure/foundry/openai/how-to/provisioned-get-started) — 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.