# AI Gateway Follow-Ups: Retry Storms, Budgets and Cache Poisoning (2026)

> The production half of the AI gateway lesson: retry storms and failover, tenant budgets and fairness, streaming and token accounting, cache poisoning, security, cost, rollout and the follow-up questions interviewers ask.

*Source: https://www.infowok.com/ai-gateway-design-follow-ups/ · Navmeet Kaur · Published October 24, 2026*

---

The core design works on a whiteboard. Then the follow-ups start. A provider fails and the fallback provider falls over too. One team's batch job drinks the shared token quota. Halfway through an answer the client hangs up, and the bill no longer matches the provider's. A crafted prompt gets another tenant's cached reply. None of this changes the picture in [AI gateway design](/ai-gateway-design/), yet all of it decides whether the gateway survives a bad day.

This lesson takes the gateway sized in the core lesson and breaks it. Its assumed load is 116 calls a second at peak, 928 open streams and about 13.2 million tokens a minute.

<Prerequisites>

- [AI gateway design](/ai-gateway-design/): the request path, the sizing arithmetic and the semantic cache.
- [Design a rate limiter](/design-a-rate-limiter/): token buckets and approximate shared counters.

</Prerequisites>

<Callout type="note" title="Remember from the core lesson">
Why is a gateway sized by open streams and not CPU? Each call waits seconds for the model, so open streams equal calls per second times seconds per answer.
</Callout>

<KeyTakeaways>

- Retries multiply across layers. Retry at one layer, with a budget, and check the failover target has room before sending traffic to it.
- Reserve a worst-case cost, stream through, then settle. Budgets are approximate by design, and the overshoot has a computable bound.
- Fairness needs weighted shares and concurrency caps, not only a total.
- A semantic cache is fuzzy matching, so it can be attacked. Partition it and keep it off for risky routes.
- Say what you would roll out first and which signal would make you stop.

</KeyTakeaways>

<LessonOutcome>

Stress-test an AI gateway: stop retry storms, set tenant budgets and fair shares, meter streamed answers correctly, defend the cache, and answer the usual follow-ups in one line each.

</LessonOutcome>

## Why Can Failover Make an Outage Worse?

Provider A starts failing. Each app SDK retries 3 times and the gateway retries 3 times, so one user call becomes 9 calls to a provider that is already down.

The Google SRE book makes the same point: [with 3 retries at each of four layers, "a single user action may create 64 attempts (4^3)" on the database](https://sre.google/sre-book/addressing-cascading-failures/). The Amazon Builders' Library [reports a 243x load increase](https://builder.aws.com/content/3EumjoZascWd1oZiEgL8ORlv3qE/timeouts-retries-and-backoff-with-jitter) when every layer retries, and says its best practice is to retry at a single point in the stack.

![Two panels: when the app SDK and the gateway both retry three times, one call becomes nine; retrying at one layer with a retry budget keeps it to at most three and stops once the budget is spent](./ai-gateway-design-follow-ups-retries.svg)

The fixes, in order of value:

- **One retry layer.** The gateway retries, the SDK does not.
- **A retry budget.** The SRE book suggests [a server-wide cap, for example 60 retries a minute in a process](https://sre.google/sre-book/addressing-cascading-failures/). Past it, fail the request.
- **Backoff with jitter.** Add random delay so retries do not arrive together.
- **Honest triggers.** Kong's plugin [does not fail over on client errors](https://developer.konghq.com/plugins/ai-proxy-advanced/), and you list the codes (such as `http_429`) that should.

Now the second storm: the failover itself. Peak is 116 calls a second. Assume provider B's quota allows 60 calls a second.

```text
116 sent to B, 60 allowed
116 - 60 = 56 a second get 429
each retries 3x: up to 168 more
```

Provider B is now overloaded by your own gateway. So check the target's headroom before failing over, and **shed** low-priority calls first, meaning refuse them early to protect the rest.

Build a failover ladder in this order (judgement):

1. A smaller model at the same provider.
2. A second provider.
3. A self-hosted model.
4. A stored answer with a notice that it is degraded.

<Callout type="warning" title="Fallback changes the bill">
OpenRouter [bills at the model that finally answered](https://openrouter.ai/docs/guides/routing/model-fallbacks), so a silent fallback to a dearer model changes your cost. Log which target served each call.
</Callout>

One more rule: never fail over after the first byte. If the stream is already flowing, the user has seen text, and a second provider would start a different answer. Fail over before the first chunk, and after it return an error event.

## How Do You Share Budgets Between Tenants?

A tenant needs two limits, because they stop different harms. A **budget** caps money over a period. A **rate share** caps tokens a minute, so one tenant cannot starve the rest. LiteLLM's docs show the shape: [`max_budget` and `budget_duration` per key, team and user, plus `tpm_limit` and `rpm_limit`](https://docs.litellm.ai/docs/proxy/users).

<Callout type="tip" title="Predict first">
Assume three gateway instances each let up to 20 requests run per tenant, a request's worst-case cost is $0.05 and the tenant's daily budget is $400. How far past the budget can the tenant spend if every instance checks a slightly stale counter?
</Callout>

Reveal it with arithmetic, an upper bound in the worst case.

```text
3 instances x 20 in flight
  x $0.05 = $3.00 overshoot
$3 / $400 = 0.75 percent
```

So an approximate counter is fine when in-flight work is small against the budget, and the bound shrinks with a lower concurrency cap. This is the same trade as a rate limiter, and a hard cap can still fail closed.

Fairness needs weights. Assume the provider gives you 8 million tokens a minute and four tenants have weights 4, 2, 1 and 1. Assume each share is then 4, 2, 1 and 1 million. If a tenant is idle, others may borrow its share and give it back when it returns (judgement). Add a per-tenant concurrency cap so one slow tenant cannot hold every connection.

## How Do You Meter a Streamed Answer?

Streaming makes the usual bill-after-the-call model fragile, because the total is known only at the end. Use **reserve, stream, settle**.

![Four steps: reserve the worst-case cost, stream chunks through while counting, read the final usage figure, then settle by refunding the difference; if the stream breaks, cancel the call and charge your own count](./ai-gateway-design-follow-ups-metering.svg)

Reserve input tokens plus `max_tokens`. Assume 1,500 input tokens and a cap of 4,000 output tokens, so the reserve is 5,500. Assume the answer used 1,900, so settling refunds 3,600.

Where does the real total come from? The OpenAI API [only sends usage in a final chunk when `stream_options.include_usage` is set, with an empty `choices` list, and warns the chunk may be missing if the stream is interrupted](https://github.com/openai/openai-openapi).

Claude's stream reports [cumulative `usage` in the `message_delta` event](https://platform.claude.com/docs/en/build-with-claude/streaming), and can also send `ping` events and an [error after a 200 status](https://platform.claude.com/docs/en/api/errors). So a gateway should:

- Ask for usage on every streamed call, and handle the empty-choices chunk.
- Count what it forwarded, as a fallback when no final figure arrives.
- Cancel the provider call when the client disconnects, or the spend continues unseen.
- Mark partial records, so billing can tell them apart.

The latency rule from the core lesson still holds: forward chunks at once. Any check that needs the whole answer runs out of band.

## Can the Cache Be Poisoned?

A semantic cache treats nearby embeddings as the same question, so it is a fuzzy match. A 2026 paper (ICML 2026) argues that this locality [conflicts with collision resistance, and reports an 86 percent hit rate for response hijacking](https://arxiv.org/abs/2601.23088) with a crafted input that transfers across embedding models. The OWASP LLM list names this area as LLM08, [Vector and Embedding Weaknesses](https://genai.owasp.org/llm-top-10/).

Defences are judgement, ranked by effect:

1. **Partition the key** by tenant, model and system-prompt version, so one tenant's entries never answer another's.
2. **Exact match only** on routes with money, safety or identity at stake.
3. **Do not cache** answers that contain personal data or tool results.
4. **Short lifetimes** and an invalidate button for bad entries.
5. **Sample hits** each week and compare them with fresh answers, so threshold drift shows up before users report it.

Contrast the default of one hosted gateway: its cache key is [a SHA-256 hash of the provider, endpoint, model, auth header and full body](https://developers.cloudflare.com/ai-gateway/configuration/caching/), an exact match with no fuzziness to exploit and a lower hit rate.

## What About Security, Cost and Rollout?

- **Security.** Keep provider keys in the gateway and hand callers their own scoped keys, so a leak is revocable per tenant. OWASP lists [LLM01 Prompt Injection and LLM10 Unbounded Consumption](https://genai.owasp.org/llm-top-10/): cap `max_tokens` and budgets per tenant, and treat logged prompts as personal data with a retention limit.
- **Cost.** Levers in order: cache, route simple calls to a smaller model, cap output length. If 30 percent of spend moves to a model that costs a fifth (an assumption), the $6,200 a day from the core lesson falls by 0.3 x 6,200 x 0.8 = $1,488.
- **Rollout.** Mirror traffic to a new route in shadow mode, canary by tenant, keep configuration versioned with a kill switch, and remap aliases to migrate models. The signal to stop is a rise in errors or in wrong-hit reports.

## Practice: Provider Outage Drill

<Exercise title="Plan the first ten minutes" time="15 min">

Provider A fails at peak (116 calls a second). Provider B allows 60 calls a second. Calls are 70 percent interactive and 30 percent batch. Write the gateway's response.

<Hint>

Interactive calls are about 81 a second. Compare that with B's room.

</Hint>

<Expected title="One reasonable answer">

```text
116 x 0.7 = 81 interactive
81 > 60: even B cannot take it
```

Pause batch first (35 a second), since it can wait. That still leaves 81 interactive against 60 of room, so shed the excess with a clear 503 and `Retry-After`, or route the simple ones to a smaller model on B. Open the circuit on A for a cooldown, and keep retrying A with a probe, not full traffic. All figures are assumptions to replace with your own.

</Expected>

</Exercise>

<InInterview>

Answer each follow-up with a trade-off, not a tool name.

| They ask | Answer in one line |
|---|---|
| Provider is down? | Fail over before first byte, check headroom |
| Retries at every layer? | One layer, a budget, jitter |
| One tenant is greedy? | Weighted shares, concurrency cap, budget |
| Stream metering? | Reserve, count, settle; handle no final usage |
| Client hangs up? | Cancel upstream, bill your count |
| Cache returns wrong answer? | Threshold, partition, exact on risky routes |
| Cache poisoning? | Tenant-partitioned key, short TTL |
| Budget store down? | Last good config, local cap, hard caps fail closed |
| Model retired? | Remap the alias, canary it |
| Cut cost 30 percent? | Cache, smaller model, output caps |
| 10x traffic? | More instances, second provider, raise tier |
| Audit and privacy? | Per-tenant logs, retention limit |

A strong answer at senior level names failure modes unprompted. A staff answer adds fairness, security and a rollout plan, and says when not to build.

</InInterview>

## Check Yourself

<CheckYourself>

<Question q="Why should a failover check the target's headroom first?">

Moving all traffic at once can exceed the second provider's quota, so it answers 429 and the retries make both providers worse. Check headroom, shed low-priority calls and consider a smaller model.

</Question>

<Question q="A stream ends without a final usage chunk. What does the gateway charge?">

Its own count of the tokens it forwarded, marked as partial. It also cancels the provider call so spend does not continue.

</Question>

<Question q="What changes if the cache is shared by two tenants?">

A crafted prompt could return the other tenant's cached answer. Partition the cache key by tenant and model, and use exact match on risky routes.

</Question>

</CheckYourself>

## In One Picture

```text
Retries   one layer, a budget
Failover  before first byte,
          check headroom
Budgets   reserve, stream, settle
Fairness  weights + concurrency
Cache     partition, exact on risk
```

## Conclusion

The core gateway is a proxy with a few shared stores. The production answer is everything around it: retries that do not multiply, failover that checks room, budgets with a known overshoot, honest streaming accounting and a cache that cannot be talked into lying. Name the accuracy you gave up each time.

**Which of these probes would you stumble on first?** Tell us in the comments.

<NextSteps>

- **Back to the core design:** [AI gateway design](/ai-gateway-design/) for the request path, sizing and semantic cache.
- **The limiter underneath:** [Design a rate limiter](/design-a-rate-limiter/) and its [follow-ups](/design-a-rate-limiter-follow-ups/).
- **Governance above it:** [AI control plane architecture](/ai-control-plane-architecture/).

</NextSteps>
