Skip to main content

Diagnose Azure OpenAI latency issues, identify noisy-neighbor effects, and improve application performance

Diagnostic PlaybookAzure OpenAIStandard / PAYGAzure MonitorFront Door Failover

How To Diagnose Noisy-Neighbor Latency in Azure OpenAI Standard Deployments

Your p99 tripled overnight. You changed nothing. The status page is green. This is the failure mode nobody wants to admit is real on a shared platform — regional demand from other tenants degrading your latency — and the fix isn't to complain, it's to prove it with the right metric, then route around it.

The failure signature this guide resolves
# Azure Monitor — the metric that reveals it (measured on the Azure OpenAI resource):
Metric: AzureOpenAITimeToResponse                          split by ModelDeploymentName
                                                           aggregation Avg + P99
                            P50        P95        P99
  yesterday 09:00-10:00   340 ms   1,240 ms   1,880 ms
  today     09:00-10:00   410 ms   3,910 ms   8,720 ms      <-- 4.6x on P99

# Prompt/completion token counts unchanged (this is the sanity check):
Metric: ProcessedPromptTokens          median 812  →  media 807   (identical)
Metric: GeneratedCompletionTokens      median 246  →  media 241   (identical)

# So the same-shape requests are now much slower. Occasionally:
HTTP/1.1 429 Too Many Requests
Retry-After: 47                                     <-- capacity, not your quota

# And the status page says:
Azure OpenAI in [your region]:  ✓ Healthy         <-- because nothing is DOWN.
                                                     It is just slow. For you. Right now.

Symptom: First-token latency spikes on unchanged code and unchanged request shapes.  Failure point: Client → regional Azure OpenAI standard endpoint → shared capacity pool → a neighboring tenant's traffic surge you cannot see.  Default platform behaviour: Standard (PAYG) deployments share capacity across all tenants in a region. Quota does not guarantee capacity — Microsoft's own documentation states this plainly. When a region is hot, your allocation is admitted but not necessarily served promptly.

TimeToResponse
AzureOpenAITimeToResponse — the metric to trust. Measures first-token latency for streaming, whole-response for non-streaming
Not guaranteed
On standard deployments, quota controls admission but does not guarantee throughput or capacity. Microsoft says so
Seconds
Front Door failover happens in seconds via anycast health probes. DNS-based failover takes minutes because of TTL caching
PTU
The only true escape from noisy neighbors. Reserved capacity with a latency SLA — but paid for even when idle

There is a category of production incident where every dashboard you own says everything is fine and yet your users are watching a spinner. Nothing changed on your side — no deploy, no config drift, no traffic spike. The Azure status page is a wall of green ticks. Your Application Insights availability is 100%. And yet the p99 latency of your chat endpoint has tripled since breakfast, and you have no obvious lever to pull. This is the shape of a noisy-neighbor incident on a shared cloud service, and the reason it goes so often unresolved is not that the problem is mysterious. It is that most teams are looking at the wrong metric, and even when they find the right one, they reach for the wrong tool to route around the damage.

Figure 1 — What "shared tenant noise" actually looks like at your endpoint
ONE REGION, MANY TENANTS, ONE SHARED CAPACITY POOLtenant Atenant B (huge)tenant CYOUtenant DSHARED CAPACITY POOLStandard / PAYG deploymentquota admits requestsbut does not guaranteethroughput or capacity(Microsoft docs, verbatim)YOUR ENDPOINT200 OK · but SLOWTimeToResponse spikesYOUR USERSsee a spinnerfor 8+ secondsSTATUS PAGE: ✓ HEALTHY · AVAILABILITY: 100% · YOUR CODE: unchanged · YOUR TRAFFIC: unchangedNo dashboard is lying — nothing IS down. The service is just slow for you, right now, because someone else is loud.DIAGNOSTIC PATH: TimeToResponse ↑   ·   TokenCounts flat   ·   TBT ↑ under load   =   noisy-neighbor, not youSame-shape requests, more time to answer. This trio distinguishes shared-pool contention from a real regression on your side.
On a standard (PAYG) Azure OpenAI deployment, all tenants in a region draw from a shared capacity pool. Your quota gets your request admitted; it does not guarantee it gets served promptly. When a neighboring tenant surges, your first-token latency rises while every other signal on your side stays perfectly flat.
01The Metric to Trust: Not "TTFT," but AzureOpenAITimeToResponseCorrection

Everyone talks about time to first token, but "TTFT" is not the name of an Azure Monitor metric. Reaching for a metric called TTFT and being unable to find it is the first place a lot of noisy-neighbor investigations stall. Microsoft's own latency documentation is precise about which metric to actually use, and the distinction matters.

Metric (Azure Monitor)What it measuresWhen to use it
AzureOpenAITimeToResponseFirst-token latency for streaming; whole-response time for non-streamingDefault choice. Absolute latency your customers experience
AzureOpenAINormalizedTBTInMSNormalized time between tokens — per-token generation throughputJudges the steady-state of the stream. Rises when the deployment is under load
AzureOpenAINormalizedTTFTInMSNormalized time to first byte, adjusted for prompt sizeOnly when comparing first-token efficiency across prompts of different sizes
ProcessedPromptTokensPrompt token count per requestSanity check — always pair with a latency metric (Section 2)
GeneratedCompletionTokensCompletion token count per requestSanity check — a longer answer is slower and that is not a regression
Azure OpenAI RequestsTotal API callsConfirms traffic shape didn't change on your side

The guidance from Microsoft is direct: for diagnosing absolute latency that customers experience, use AzureOpenAITimeToResponse. Use the normalized TTFT metric only when you specifically need to compare across differently-sized prompts. Most incident investigations do not need normalization — they need the number the user is actually waiting on.

Split by ModelDeploymentName, and use P99

Two configuration details that are easy to skip and turn every dashboard into noise. First: always split by ModelDeploymentName — one hot deployment can drag your averages and hide which one is actually the problem. Second: use P99 aggregation, not Avg. Noisy-neighbor damage lives entirely in the tail. Averaging hides the very requests you are trying to see, and you will convince yourself the problem is imaginary while a tenth of your users are watching an 8-second spinner.

02The Sanity Check Before the Support Ticket: Token CountsDiagnosis

Before you conclude anything, do the one check that separates a real latency regression from a request-shape change on your side. Microsoft's guidance is blunt: latency without token context isn't actionable. A five-second response that generates 2,000 tokens is a very different animal from a five-second response that generates 50. If your team just changed a prompt, added a chain-of-thought step, or bumped max_tokens, your latency has "risen" for the perfectly boring reason that you are asking the model to do more work.

The rule of thumb, stated by Microsoft in their own latency documentation: total end-to-end response time scales with the number of generated tokens, so an increase in it is often fully explained by an increase in output tokens — not by a system performance issue. Always check token counts before you conclude that there's a latency regression.

ObservationPrompt tokensCompletion tokensConclusion
P99 up, tokens up↑ or ↑↑Not a regression. Your requests got bigger
P99 up, prompts up onlyflatPrefill got heavier. Trim the prompt or check RAG bloat
P99 up, completions up onlyflatAnswers got longer. Cap max_tokens or shorten instructions
P99 up, both flatflatflatReal latency regression. Now investigate the region
Do the token check first, every time

This is the sanity check that prevents the embarrassing support ticket where the answer comes back "your median completion tokens increased 60% overnight — this is a prompt problem, not a platform problem." Ninety percent of the time, when a team is certain the platform slowed down, the shape of their traffic changed and nobody looked. Sixty seconds of KQL will save you a day of finger-pointing. Only when both token counts are flat and latency is up do you have a signal that is genuinely about the shared pool.

03You vs the Region vs the Model: Three Distinguishable CulpritsDiagnosis

Once the token counts clear you of self-inflicted regression, there are three remaining suspects. They present differently and each has a different fix, so getting the wrong one is expensive.

CulpritSignatureHow to confirmFix
Your side (still)TimeToResponse up in one deployment; tokens flat but request rate is upCheck RPM vs quota; check other clients on the same keyRate-limit at the client; add a queue; scale up quota
Shared regionTimeToResponse up across all your deployments in a region; TBT up; other regions on the same subscription are fineDeploy the same model in a second region and compare in parallel — this is decisiveFailover to another region (Sections 6 & 7)
Specific modelOnly one model (e.g. a newly-launched or hot model) is affected; other models in the same region are fineCompare TimeToResponse across models in the same regionSwitch to a less-hot model version; or PTU (Section 8)
The decisive test: run a shadow deployment in another region

You cannot diagnose a regional problem from a single region — every metric you have is coming from the region you suspect. The only test that cannot lie is to send the same requests to the same model in a different region, simultaneously, and compare. If the other region is fast, you have your answer, and you already have the fallback endpoint built. If the other region is also slow, it is more likely to be a model-wide event or a change on your side that a token count didn't catch. This shadow-region setup is not just a diagnostic — it is the first stage of the fix.

Architectural Topology: Failing vs Remediated

ComponentFailing configuration (current)Remediated configuration (fix)
EndpointDirect client → single-region AOAI resourceClient → Front Door → prioritised multi-region origins
Region postureSingle region; no comparison signal exists≥ 2 regions with the same model, one hot, others standby
Failover control-planeNone — or DNS-based (Traffic Manager) with TTL delaysFront Door priority routing; HTTP probes; seconds to shift
Failover triggerNone — you notice when users complainHealth-probe failure OR alert on P99 TimeToResponse
Latency metricAverages; ignored until users complainP99 AzureOpenAITimeToResponse, split by deployment, alerted
Token accountingLatency looked at aloneAlways paired with prompt + completion token counts
Retry policyRetries on 429 hammer the same hot region harderAPIM circuit breaker; retries land on a different origin
Business-critical baselineStandard only — noisy-neighbor exposure by designPTU floor + PAYG burst — noise cannot touch the SLA path
05Fix 1 — Make the Noise Visible (KQL & Alerts)Diagnosis

You cannot route around damage you cannot see, and by default you cannot see this damage. It hides in averages, gets lost in unsplit metrics, and never fires an alert because nothing is technically broken. Wire the metrics correctly, and the noisy-neighbor signature becomes obvious the moment it starts.

KQL — the noisy-neighbor signature, expressed as a query// Requires diagnostic settings on the AOAI resource -> Log Analytics. // This is the trio: latency up, tokens flat, request shape unchanged. AzureMetrics | where TimeGenerated > ago(2h) | where ResourceProvider == "MICROSOFT.COGNITIVESERVICES" | where MetricName in ( "AzureOpenAITimeToResponse", "AzureOpenAINormalizedTBTInMS", "ProcessedPromptTokens", "GeneratedCompletionTokens" ) | summarize p99_latency = percentile(iif(MetricName == "AzureOpenAITimeToResponse", Maximum, real(null)), 99), p99_tbt = percentile(iif(MetricName == "AzureOpenAINormalizedTBTInMS", Maximum, real(null)), 99), median_prompt = percentile(iif(MetricName == "ProcessedPromptTokens", Average, real(null)), 50), median_completion = percentile(iif(MetricName == "GeneratedCompletionTokens", Average, real(null)), 50) by bin(TimeGenerated, 5m), Resource | order by TimeGenerated desc // A "you are the noisy neighbour" incident: p99_latency ↑ AND tokens ↑ // A "shared tenant noise" incident: p99_latency ↑ AND p99_tbt ↑ AND tokens flat
Azure CLI — the alert that would have paged you at the start of the incident# P99 TimeToResponse over 6s for 5 minutes on any deployment. Threshold from # YOUR historical baseline — don't copy this number. Measure your own P99 first. az monitor metrics alert create \ --name "aoai-p99-latency-elevated" \ --resource-group rg-ai-prod \ --scopes "/subscriptions/.../providers/Microsoft.CognitiveServices/accounts/my-aoai" \ --condition "max AzureOpenAITimeToResponse > 6000" \ --window-size 5m \ --evaluation-frequency 1m \ --severity 2 \ --action /subscriptions/.../actionGroups/oncall-ai
Alert on the tail, not the average

An alert on the average TimeToResponse is functionally useless for this failure mode. Averages absorb spikes, and by the time a noisy-neighbor event moves the mean it has moved the tail so far that a real percentage of your users have already given up. Alert on the maximum or a high percentile, on a short window, and be prepared for a few false starts as you calibrate to your own P99 baseline. The false positives are cheap; the false negatives cost you users.

Diagnostic settings — the one-time enablement you can't skip# Diagnostic settings must be enabled to send AOAI metrics to Log Analytics — # the portal Metrics view alone won't cover you for post-incident forensics. az monitor diagnostic-settings create \ --name "aoai-to-la" \ --resource "/subscriptions/.../accounts/my-aoai" \ --workspace "/subscriptions/.../workspaces/la-shared" \ --metrics '[{"category":"AllMetrics","enabled":true}]' \ --logs '[{"category":"RequestResponse","enabled":true}, {"category":"Trace","enabled":true}]'
06Fix 2 — Multi-Region Failover with Azure Front Door (Not Traffic Manager)Correction

This is the fix, but it comes with a correction worth stating up-front: for an Azure OpenAI failover, Azure Front Door is the right control-plane, not Azure Traffic Manager. The two services can look interchangeable — both do multi-region routing, both have priority modes, both do health probing — but for a noisy-neighbor incident where the "outage" arrives and departs in minutes, the difference between them is the difference between mitigation and irrelevance.

PropertyTraffic ManagerAzure Front Door
LayerDNS (returns an IP; steps out of the way)HTTP/HTTPS at the edge (anycast, split-TCP)
Failover speedMinutes — bounded by DNS TTL caching on the client sideSeconds — no DNS propagation delay
Health probingUS-based probes only; unreliable for global anycastEdge-wide HTTP probes with configurable intervals
Best fitNon-HTTP protocols; simple failover across any endpointHTTP/HTTPS workloads — including every AOAI call

Microsoft's own best-practices documentation is direct about this choice: use either Azure Front Door or Traffic Manager, not both, and for HTTP workloads that need edge acceleration and fast failover, Front Door is the recommended service. Since every Azure OpenAI call is HTTPS, and since the whole point of this exercise is failing over in seconds rather than after DNS TTLs expire, Front Door is the natural choice.

Figure 2 — Front Door vs Traffic Manager during a noisy-neighbor spike: the timing is the whole story
t=0   a neighbouring tenant surges in your region  ·  your P99 breaks the alert thresholdt=0+30s+1 min+3 min+5 minFRONT DOORHTTP probes · edgeprobe fails3 consecutiveFAILOVER COMPLETEnext request → healthy regionSERVING FROM SECONDARY REGION AT NORMAL LATENCYusers never see the spike beyond the first ~30-45sTRAFFIC MGRDNS · TTL boundprobe fails · DNS record swapbut clients still cached to old IPCLIENTS STILL HITTING BAD REGIONTTL + resolver caching lags realitymost clients migratedstragglers still notTHE WHOLE INCIDENT IS OFTEN OVER IN 3–5 MINUTES.Front Door finishes the migration inside that window. DNS-based failover is still churning when the spike has already passed —which is worst-case: you pay for the outage AND the confused user complaints from clients that stayed on the sick region.
A noisy-neighbor spike is a transient event, so failover speed is not a nice-to-have — it is the entire property that determines whether the mitigation actually mitigates anything. Front Door's HTTP-level anycast probes shift traffic within seconds. Traffic Manager is bounded by client-side DNS caching, so a fraction of your traffic keeps hitting the degraded region long after the switch.

The topology: two regions, one Front Door, priority routing

The design is deliberately minimal. Deploy the same model into a primary and at least one secondary region. Register both as origins in a single Front Door origin group. Set priority on the primary and lower priority on the secondary. Configure health probes against the OpenAI health endpoint. That is the whole thing.

Bicep — Front Door Standard/Premium with AOAI origins and priority failoverresource profile 'Microsoft.Cdn/profiles@2024-02-01' = { name: 'afd-aoai-hybrid' location: 'global' sku: { name: 'Standard_AzureFrontDoor' } } resource endpoint 'Microsoft.Cdn/profiles/afdEndpoints@2024-02-01' = { parent: profile name: 'aoai-api' location: 'global' properties: { enabledState: 'Enabled' } } resource originGroup 'Microsoft.Cdn/profiles/originGroups@2024-02-01' = { parent: profile name: 'aoai-multi-region' properties: { loadBalancingSettings: { sampleSize: 4 successfulSamplesRequired: 2 // 2 successes flip healthy additionalLatencyInMilliseconds: 50 } healthProbeSettings: { probePath: '/health' // or a lightweight liveness path probeRequestType: 'HEAD' // HEAD reduces origin load probeProtocol: 'Https' probeIntervalInSeconds: 30 // tight, for fast detection } sessionAffinityState: 'Disabled' // AOAI is stateless — do NOT stick } } // PRIMARY — priority 1, receives all traffic when healthy. resource originEastUs 'Microsoft.Cdn/profiles/originGroups/origins@2024-02-01' = { parent: originGroup name: 'aoai-eastus' properties: { hostName: 'my-aoai-eastus.openai.azure.com' httpsPort: 443 originHostHeader: 'my-aoai-eastus.openai.azure.com' priority: 1 weight: 1000 enabledState: 'Enabled' } } // SECONDARY — priority 2. Front Door routes here only when primary is unhealthy. resource originSweden 'Microsoft.Cdn/profiles/originGroups/origins@2024-02-01' = { parent: originGroup name: 'aoai-swedencentral' properties: { hostName: 'my-aoai-swedencentral.openai.azure.com' httpsPort: 443 originHostHeader: 'my-aoai-swedencentral.openai.azure.com' priority: 2 // standby weight: 1000 enabledState: 'Enabled' } }
The keys and deployment names must match across regions

This is where multi-region AOAI deployments actually go wrong in production. Front Door is happy to route to either region, but your API keys are per-resource and your deployment name paths must be identical on both sides — /openai/deployments/gpt-4o/chat/completions?api-version=... must resolve on both origins. Either standardise deployment names across regions (recommended) or handle key rotation through an APIM layer that injects the correct key per origin (Section 7). Skipping this is how failover works in your test and fails in your incident.

Regions are not fungible — check availability

Not every model is available in every region, and quota is allocated per-region-per-subscription. Before you designate a secondary region, verify that the specific model version you use in production is deployable there, and that you have — or can get — the quota you actually need. Two regions serving different model versions during a failover is a subtler outage than one region being slow, because your responses change shape at the same moment.

07Fix 3 — Circuit Breakers and Smart Load Balancing at APIMReinforcement

Front Door handles outage-shaped failure well — an unresponsive origin, a 5xx storm — but noisy-neighbor damage is subtler. Requests still return 200s, just slowly. The health probe may pass while your users are watching a spinner. This is where Azure API Management earns its place in the topology: it can react to signals Front Door does not see, including elevated 429 rates and per-token latency, and it can implement the retry logic Front Door deliberately does not.

APIM policy — backend circuit breaker + retry across two AOAI backends<policies> <inbound> <base /> <!-- Set the primary backend. --> <set-backend-service backend-id="aoai-eastus" /> <!-- Inject the correct region's key. Managed identity is cleaner still. --> <set-header name="api-key" exists-action="override"> <value>{{aoai-eastus-key}}</value> </set-header> </inbound> <backend> <!-- Retry twice on the primary. If BOTH retries fail, fall through. --> <retry condition="@(context.Response.StatusCode == 429 || context.Response.StatusCode >= 500 || context.Response.StatusCode == 408)" count="2" interval="1" max-interval="4" delta="1" first-fast-retry="false"> <forward-request buffer-request-body="true" timeout="60" /> </retry> </backend> <on-error> <!-- Fallback: switch backend + key and retry ONCE on the secondary. --> <choose> <when condition="@(context.Response.StatusCode == 429 || context.Response.StatusCode >= 500)"> <set-backend-service backend-id="aoai-swedencentral" /> <set-header name="api-key" exists-action="override"> <value>{{aoai-swedencentral-key}}</value> </set-header> <retry condition="@(context.Response.StatusCode >= 500)" count="1" interval="0"> <forward-request buffer-request-body="true" timeout="60" /> </retry> </when> </choose> </on-error> </policies>
Configure the backend's circuit breaker, don't roll your own

APIM supports a first-class circuit breaker on backend definitions — configure it on the backend, don't try to reimplement it in policy. Set a failure-rate threshold and a trip duration; when tripped, APIM stops sending requests to the sick origin altogether for the configured window, so retries land on the healthy secondary immediately rather than hammering the hot region repeatedly and making things worse. Retries without a breaker are indistinguishable from a self-inflicted DDoS.

Honour Retry-After instead of a fixed backoff

When Azure OpenAI returns 429, the response includes a Retry-After header (sometimes as x-ms-retry-after-ms in milliseconds). Read it — your backoff is being told to you by the service. A retry-in-milliseconds implementation that ignores this header and uses a fixed exponential ladder will retry too fast, get throttled again, and take longer to recover than if it had simply waited what the platform told it to.

08Fix 4 — When Standard Isn't Enough: PTU as the Real EscapeEscape Hatch

Everything above is mitigation on a shared pool. The only fix that actually removes noisy-neighbor exposure — because it removes the shared pool — is Provisioned Throughput Units (PTU). On a standard (PAYG) deployment, quota only controls admission logic; it does not enforce throughput and it does not shield you from other tenants. On a provisioned deployment, you allocate a fixed amount of model processing capacity to your endpoint, and no other tenant can borrow it.

PropertyStandard (PAYG)Provisioned (PTU)
Capacity modelShared pool across all tenants in regionReserved for your deployment
Quota semanticsAdmission control only — no throughput guaranteeEnforced throughput budget
Noisy-neighbor exposureYes. By designNo. That is the point
Latency stabilityVariable; tail dominated by regional demandPredictable; independent of neighbouring tenants
Cost shapePer token consumedPer hour reserved, whether used or not
Best-fit workloadBursty, cost-sensitive, dev & POCBusiness-critical, latency-critical, steady baseline

The honest recommendation is not to move everything to PTU — that is often financially indefensible. It is a hybrid: PTU-sized to your baseline, with PAYG as the burst layer. Business-critical, latency-critical traffic lands on the reserved capacity where noisy neighbours cannot reach it. Everything else — internal tools, batch jobs, low-priority background enrichment — runs on standard and takes the noise on the chin.

Route deliberately — don't send burst to PTU

The hybrid only works if you route to PTU intentionally. Sending all traffic there defeats the point and blows the budget; leaving PTU idle while PAYG is throttled defeats it too. Route by workload class at the ingress layer: user-facing chat and voice → PTU; internal automation, batch, and offline evaluation → PAYG. If your PTU is under-utilised outside peak hours, use the same client-side retry logic you built for 429s to spill from PAYG to PTU when the standard endpoint is throttled — the exact opposite of the usual pattern, and one that quietly makes the whole system more resilient.

09Anti-Patterns: Fixes That Look Right and Aren'tTraps

Because this failure mode is confusing, the wrong instincts are strong. These are the fixes teams reach for that either make things worse or paper over the real problem.

Anti-patternWhy it feels rightWhy it isn't
Aggressive client retries"Just retry the slow call!"A slow call retried is another slow call in a hot region. Retries without a breaker amplify contention
Requesting more quota"429s stopped, so quota was the issue"Quota controls admission, not throughput. More quota does not create more shared capacity
Traffic Manager priority failover"It's DNS-simple and cheaper"DNS TTL caching makes failover minutes-slow. Noisy-neighbor incidents are minutes-long
Streaming everything to hide TTFT"At least users see tokens sooner"It masks the metric while making the underlying starvation worse. TBT still rises; users still wait for the full answer
Reducing max_tokens as the fix"Shorter answers = lower latency"Correct answer to a different question. Helps overall TTLT but does nothing for first-token latency under contention
Waiting for the status page to admit it"Microsoft will call it out"Nothing is down. A shared-pool spike does not become a status incident. Diagnose it yourself
The truth about the status page

It bears saying plainly: the Azure status page reports outages, not degradations. A region that is answering every request with a 200 in eight seconds instead of one is not an outage by the platform's definition, and it will not appear there. If your users are having a bad time and the status page is green, that combination is not the platform gaslighting you — it is a statement about the definition of the word "healthy." Trust your metrics.

Validation & Verification: Confirm the Fix

Because the failure is transient and invisible from outside your telemetry, validation requires either an active test or a passive proof that the failover actually engaged the last time the noise happened. Do both.

Step 1 — Prove Front Door fails over in seconds (active test)# Disable the primary origin to simulate the region becoming unresponsive. # Front Door probes should detect the failure and shift traffic within # seconds, NOT minutes — that is the whole reason for choosing it. az afd origin update \ --resource-group rg-ai-prod \ --profile-name afd-aoai-hybrid \ --origin-group-name aoai-multi-region \ --origin-name aoai-eastus \ --enabled-state Disabled # Immediately start hammering the Front Door endpoint at ~10 rps and # watch the response header set by your origin (e.g. x-region) or a # custom debug header showing which origin served the request. while true; do curl -s -o /dev/null -w "%{http_code} %{time_total}s\n" \ -H "api-key: $KEY" \ "https://aoai-api.z01.azurefd.net/openai/deployments/gpt-4o/chat/completions?api-version=2024-10-21" \ -d '{"messages":[{"role":"user","content":"ping"}],"max_tokens":5}' sleep 1 done # PASS: after 30-60 seconds ALL responses are 200s from the secondary. # FAIL: continued 5xx or timeouts > 2 minutes means probes / priority # or the secondary region's deployment name/key are misconfigured. # Re-enable the primary and confirm traffic returns to it. az afd origin update ... --enabled-state Enabled
Step 2 — Prove your metric wall would catch the next incident (KQL)// Run this after a week. It should surface any recent latency spike // on the primary region, matched with token counts that stayed flat. // If there was a spike and this query does not surface it, your // monitoring is not wired up correctly. AzureMetrics | where TimeGenerated > ago(7d) | where ResourceProvider == "MICROSOFT.COGNITIVESERVICES" | where MetricName == "AzureOpenAITimeToResponse" | summarize p99 = percentile(Maximum, 99), p50 = percentile(Average, 50) by bin(TimeGenerated, 5m), Resource | extend tail_ratio = p99 / p50 | where tail_ratio > 8 // tail blew out relative to median | order by TimeGenerated desc // PASS: query returns rows for any incident you can remember, and // your alert history shows a page fired at that time. // FAIL: no rows, or rows but no alert = alert threshold is wrong.
Step 3 — The token-count sanity check, automated# This is the check that should run as part of every "AOAI is slow" # investigation, before anyone escalates. It answers the FIRST question: # is our own traffic to blame? AzureMetrics | where TimeGenerated between (ago(2h) .. now()) | where MetricName in ("ProcessedPromptTokens", "GeneratedCompletionTokens") | summarize median_val = percentile(Average, 50) by MetricName, bin(TimeGenerated, 10m) | evaluate pivot(MetricName, max(median_val)) | order by TimeGenerated desc # PASS (for concluding noisy-neighbor): both series are flat. # FAIL (for that conclusion): either rose, in which case the "latency # regression" is a change on YOUR side and the fix is different.
What "fixed" actually means here

Three conditions must hold together, and none of them can be substituted for the others. One: you can prove Front Door shifts traffic in seconds when the primary origin is unhealthy, because that is the whole reason for choosing it over Traffic Manager. Two: your AzureOpenAITimeToResponse P99 alert would have fired at the start of the last incident you remember — if it did not, your threshold or your split-by-deployment configuration is wrong and you will miss the next one. Three: the token-count sanity check is a first-class step in your runbook, so you never again escalate a "latency regression" that turns out to be a prompt change. Miss any of those three and the next noisy-neighbor spike will play out exactly like the last one.

Key Takeaways

The metric is AzureOpenAITimeToResponse, not "TTFT." Split by ModelDeploymentName and use P99. Averages hide the entire failure mode.
Latency without token context isn't actionable. Always check prompt and completion token medians before concluding it is a regression. Most "AOAI got slower" incidents are prompt changes.
Quota admits, it does not guarantee. On standard (PAYG) deployments, your quota gets your request into the queue — it does not shield you from a shared pool being loud.
Use Azure Front Door, not Traffic Manager. Noisy-neighbor spikes last minutes; DNS-based failover is bounded by TTL and lags reality. Anycast HTTP probes shift traffic in seconds.
Retries without a circuit breaker are self-inflicted DDoS. Configure the breaker on the APIM backend and honour Retry-After. Retries land on a healthy origin, not the sick one, repeatedly.
PTU is the only real escape from shared-pool noise. Hybrid PTU + PAYG for business-critical baseline plus burst is usually the right economic answer, not "all PTU" or "all PAYG."
The status page won't call it. Nothing is down; nothing was outaged. Trust your P99 metric and your token-count check, or the failure mode survives forever.

Frequently Asked Questions

Is "TTFT" a metric I can find in Azure Monitor for Azure OpenAI?
Not by that literal name — this is where a lot of investigations stall. The metric you actually want is AzureOpenAITimeToResponse, which Microsoft's own latency guidance recommends for diagnosing absolute latency: it measures first-token latency for streaming and whole-response time for non-streaming requests. There is also AzureOpenAINormalizedTTFTInMS for comparing first-token efficiency across differently-sized prompts, but for most investigations you want the un-normalized AzureOpenAITimeToResponse — the actual time your users are waiting. Split it by ModelDeploymentName and aggregate as P99. Averages will hide the noisy-neighbor damage entirely.
Should I use Azure Traffic Manager or Azure Front Door for multi-region AOAI failover?
Azure Front Door, and Microsoft's own best-practices documentation is direct that you should use one or the other, not both. The reason for choosing Front Door over Traffic Manager for this specific workload is speed: Front Door operates at the HTTP layer with anycast edge probes and can shift traffic within seconds. Traffic Manager is DNS-based, so failover is bounded by client-side DNS TTL caching — even after Traffic Manager marks an endpoint unhealthy, clients that resolved DNS earlier keep hitting the sick region until their caches expire, which can take minutes. Since a noisy-neighbor spike often only lasts minutes, DNS-based failover is frequently still churning when the incident has already ended. That is the worst of both worlds. Traffic Manager remains the right tool for non-HTTP protocols; for AOAI, use Front Door.
If quota controls admission but doesn't guarantee capacity, is more quota useless?
Not useless, but not the fix for this particular failure. More quota lets you make more requests before hitting a 429 — genuinely useful when the problem is your own traffic growing. It does not, however, expand the shared capacity pool your requests draw from. If a neighbouring tenant is loud, you can raise your quota to the moon and it will not make your requests come back faster; you will just be allowed to queue more of them at the same slow rate. This is the misconception behind a lot of unhelpful support tickets. Diagnose the root cause first — token counts flat, P99 up across all your deployments in one region — and only then decide whether the answer is quota, failover, or PTU.
When does it actually make sense to move to Provisioned Throughput (PTU)?
When the workload is business-critical, latency-sensitive, and has a predictable steady baseline. PTU removes noisy-neighbor exposure entirely because it allocates reserved model processing capacity to your deployment — no other tenant can borrow it. The cost model is per hour reserved rather than per token, so it only makes sense if you would utilise a meaningful fraction of that capacity most of the time. The honest recommendation for most teams is not "move to PTU" but a hybrid: PTU sized to the baseline for user-facing traffic that must have predictable latency, plus PAYG for burst, internal tools, and batch jobs where taking noise on the chin is cheaper than reserving capacity you rarely need. Route deliberately at ingress; sending all traffic to PTU blows the budget without improving anything.

Popular posts from this blog

Learn how to use Azure Chaos Studio to simulate data center outages, test Azure OpenAI failover, and validate AI app resiliency using KQL and CLI workflows

Resiliency Testing Chaos Studio Zone Down Azure OpenAI Failover Testing AI Resiliency: Using Azure Chaos Studio to Simulate Data Center Outages on Your LLM Every multi-region Azure OpenAI architecture diagram has a failover arrow drawn on it. Almost none of them have ever actually been triggered. The arrow is a hypothesis, confirmed only by a real outage — unless you deliberately cause a controlled one first, on your own schedule, with a rollback plan, instead of finding out during an incident that the failover you designed never quite worked the way the diagram promised. The failure signature this guide resolves # The gap this article closes — a real architecture review finding: Design doc, page 4: "In the event of a regional outage, Azure Front Door automatically routes traffic to the secondary Azure OpenAI deployment in West Europe, with an expected failover time under 60 seconds." Verification performed to support this claim: NONE. Last time this path was ac...

Improve AI application performance by reducing latency, optimizing embeddings, and lowering cloud inference costs

Performance Fix Foundry Local 1.2 Linux ARM64 Embeddings Offline ASR The Edge Latency Drop: Fixing Latency Spikes by Offloading Embeddings to Foundry Local 1.2 You are paying a full cloud round trip — network, TLS, queue, throttle risk — to turn a twelve-word search query into a vector. That is the most expensive way possible to do one of the cheapest computations in your stack. Foundry Local 1.2 now runs on Linux ARM64, which means embeddings and speech recognition can happen on a Raspberry Pi, a Jetson, or a Graviton instance — offline, unmetered, and in single-digit milliseconds. The failure signature this guide resolves # Application Insights — the embedding call, not the LLM, is your tail latency: name p50 p95 p99 calls/day POST /embeddings (cloud) 89 ms 412 ms 3,847 ms 1,240,000 POST /chat/completions (cloud) 940 ms 1,720 ms 2,910 ms 38,000 ^^^^^^^^ ...

Learn how to select Azure Files and Blob storage tiers, avoid early deletion fees, model costs, and automate lifecycle management for large file migrations.

Choosing the Right Azure Storage Tier for Large File Migrations The complete decision framework for storage tier selection during large file migrations — Azure Files tiers, Blob tiers, cost modelling, early deletion traps, lifecycle automation, and the 2026 changes that affect every migration running today. By Francis Avorgbedor | Azure Engineer  ·  July 14, 2026  ·  18 min read  ·  Storage Tiers · Cost Optimisation · Migration FA Francis Avorgbedor Azure Engineer  ·  SEVENAI  ·  Azure Field Notes 9 Distinct Azure storage tiers across Files and Blob — most engineers know only 3 15hrs Archive tier rehydration time at standard priority — the delay teams forget to plan for 128KB Minimum billable object size for Cool/Cold/Archive from July 2026 — a 32× trap for small files 70% How much retrieval and transaction fees add to a theoretical Archive storage bill The most expensive mistake I see on large Azure file migrations is not choosing the w...

Find the hidden Windows 10 system files consuming up to 500GB, including hibernation, shadow copies, backups, and WinSxS, with safe cleanup steps.

  The 500GB System File That Eats Your Hard Drive Something on your Windows 10 drive is consuming hundreds of gigabytes and the normal tools cannot find it. This guide identifies every known culprit — from hibernation files and shadow copies to runaway backups and the Windows component store — and tells you exactly what is safe to delete, what to leave alone, and what the commands actually do.

Learn safe methods to reset Azure virtual machines using managed disks while preserving critical workloads

How to Reset an Azure Virtual Machine to Factory Settings Using a Managed Disk Azure does not have a single "factory reset" button. What it does have is something better: the OS Disk Swap — a method that swaps out the corrupted or misconfigured OS disk for a clean Windows Server managed disk without deleting the VM, its NICs, its IP addresses, or any attached data disks. Here is how it works, when to use it, and the exact steps to execute it safely. FA Francis Avorgbedor Azure Engineer July 16, 2026 15 min read Azure VMs · Windows Server · Real-World Fix 3 Methods to achieve a clean Windows Server installation on an existing Azure VM ~15min Typical OS Disk Swap duration — VM retains its NICs, IPs, and data disks throughout 0 Data disks affected by an OS Disk Swap — data disks remain attached and untouched 1 Snapshot of the original OS disk you must take before starting — no exceptions Introduction Why Azure Does Not Have a Simple Factory Reset — and What to Do Instead On a ph...

Determine Windows 11 compatibility, upgrade requirements, costs, and performance expectations on older hardware

Can I Update My Old Computer to Windows 11 — and How Much Will It Cost? Your i7, 16GB RAM, 512GB SSD machine is powerful enough to run Windows 11 comfortably. The TPM 2.0 and Secure Boot wall is a security checkbox, not a performance ceiling. Here are two proven ways to get past it, what each one costs, and what you are trading away by doing so. $0 Cost of the Windows 11 licence if your existing Windows 10 is genuine — the upgrade remains free in 2026 2 Proven methods to bypass TPM 2.0 and Secure Boot — Rufus (easy) and Registry edit (manual) 25H2 Current Windows 11 version — all known bypass methods tested and confirmed working as of July 2026 Oct 2025 Windows 10 end of life — no more security updates. Staying on Windows 10 now carries real risk. First — Check Your BIOS Before Anything Else You Might Not Actually Need a Bypass Before running any bypass, open your BIOS and look at two settings. Many computers that fail the Windows 11 compatibility check have TPM 2.0 present in the hard...

Solve common AKS issues with practical troubleshooting techniques for networking, scaling, upgrades, and workloads

Troubleshooting Guide AKS Kubernetes Real Solutions kubectl Azure Kubernetes Service (AKS) Troubleshooting Guide: Real Solutions to Common Problems CrashLoopBackOff at 2am. Pods stuck Pending with no obvious cause. Nodes going NotReady mid-deployment. DNS resolution silently failing in production. Every AKS engineer encounters these — the difference between engineers who panic and engineers who stay calm is knowing the exact sequence of diagnostic commands to run. This guide gives you that sequence, the root cause analysis for each failure mode, and the fix. 3 commands 90% of AKS problems are diagnosed with the same three kubectl commands: describe pod, logs --previous, and get events — in that order, every time Exit 137 The exit code that tells you everything: container killed by SIGKILL — either the Linux OOM killer (memory limit exceeded) or kubelet after grace period expired 5 min The CrashLoopBackOff ceiling: Kubernetes applies exponential backoff (10s → 20s → 40s → 80s → 160s → 3...

Step-by-step guide to deploying scalable AI chatbots on Azure with OpenAI and App Service

Step-by-Step Guide Azure OpenAI App Service Production Python How to Deploy an AI Chatbot on Azure Using Azure OpenAI and App Service From zero to a production-grade AI chatbot: provision Azure OpenAI, write a streaming Flask API backend, deploy it on Azure App Service with Managed Identity, wire in conversation history and content safety, and instrument it with Application Insights — all with complete code and Terraform IaC. No API keys in environment variables. No hardcoded secrets. No half-finished PoC patterns. 7 phases This guide covers the full deployment lifecycle: architecture design → resource provisioning → backend code → App Service deployment → streaming → security → monitoring Zero keys The chatbot authenticates to Azure OpenAI using Managed Identity and DefaultAzureCredential — no API keys stored in environment variables, Key Vault, or code SSE Server-Sent Events stream GPT tokens to the browser as they generate — the same token-by-token typing effect users expect from pr...