Skip to content

Rebuild only changed routes' policy chains on xDS updates #2740

Description

@renuka-fernando

Description

The policy engine rebuilds every route's policy chain on every xDS update. Because xDS is State-of-the-World, any artifact change (deploy/redeploy/delete of any REST API, LLM proxy, etc.) pushes the full snapshot, and HandlePolicyChainUpdate unconditionally calls buildPolicyChainregistry.GetInstance → each policy's GetPolicy factory for every policy on every route. So redeploying API B re-instantiates the policy instances of unrelated APIs A, C, and so on.

This hurts stateful policies that keep per-instance state across requests. The clearest example is the backend-JWT policy (policies/backend-jwt), which caches generated upstream JWTs so an upcoming client request can reuse an already-signed token instead of re-signing. Because GetPolicy is re-invoked on every unrelated deployment today, the policy cannot simply hold that cache on its instance — it had to be engineered around the behavior:

  • GetPolicy returns a process-wide singleton (var ins) rather than a fresh instance, so the token cache and key cache survive redeploys (the code comment states it is "called on each API deployment").
  • The token cache is a single shared cache across all APIs, with every token-shaping field folded into the cache key for implicit invalidation.
  • ensureTokenCache guards against rebuilding the cache except on a genuine cacheMaxSize change.

That complexity exists solely because instances are torn down and rebuilt on every unrelated snapshot. Any policy wanting a natural per-instance cache (token caches, warm lookups, connection pools, rate-limit windows) faces the same churn.

Fix

Reconcile each snapshot against the last-applied set on the policy-engine side: rebuild only changed/new routes, reuse the existing chain and its instances for unchanged routes, and drop removed routes. Change detection uses an order-sensitive canonical-JSON signature of each route's behavioral config (excluding volatile metadata), fails safe to rebuild, and emits per-route decision logs. Implemented in 46d8ed6f2.

Outcome: redeploying one API no longer re-runs other APIs' GetPolicy factories, so policies can keep per-instance caches and state across unrelated deployments — and workarounds like the backend-JWT process-wide singleton are no longer needed for correctness.

Version

No response

Related Issue

No response

Metadata

Metadata

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions