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

Assignees

No one assigned

    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