How does OPA's bundle plugin avoid re-downloading and re-activating an unchanged bundle?
answer
- conditional HTTP, not a full fetch
- stored header replayed on the next poll
- 304 means skip download and activation
- compilation is the expensive half
- delta bundles carry data changes only
basics
~20 sOPA replays the Etag from its last successful bundle download as If-None-Match on the next poll. If the service answers 304 Not Modified, OPA skips the download and the activation and keeps serving the bundle it has.
solid answer
~50 sThe bundle plugin polls on a configurable interval, jittered between a minimum and maximum delay. On each poll it sends the `Etag` it stored from the last successful download as an `If-None-Match` header. If the bundle service replies `304 Not Modified`, OPA does nothing further: no body is transferred, no Rego is parsed or compiled, no activation happens and the active revision stays where it was. That matters more than the bandwidth saving, because activation is the expensive half — the policy has to be parsed and compiled and the data written into the store. If the service returns `200` with no Etag on every poll, OPA re-downloads and re-activates identical content forever, burning CPU proportional to policy and data size. Where the data is large and changes often, a delta bundle ships only the changed data instead of a whole snapshot.
go deeper
Know that OPA pulls its bundle from a service on a timer rather than being pushed to, and that an unchanged bundle is not re-downloaded on every poll.
Explain the conditional request precisely: the stored Etag goes back as If-None-Match, a 304 skips both transfer and activation, and a 200 triggers a parse, compile and store write.
Diagnose the quiet failure modes — a stripped Etag causing constant re-activation, or a restart during a bundle-service outage leaving an agent with no policy unless persistence is on.
Decide the fleet-wide defaults: how fresh policy has to be versus what the bundle service can carry, and whether persistence is mandatory for agents sitting in a request path.
## The polling loop The bundle plugin is a loop: wait, request, maybe activate. The wait is configurable as a minimum and maximum delay, and OPA picks a jittered interval between them so a fleet of agents does not stampede the bundle service in lockstep after a restart. The request is an ordinary HTTP GET against the configured service and resource path. What makes it cheap is that it is **conditional**. ## Conditional polling on Etag When a bundle download succeeds, OPA stores the `Etag` response header alongside the activated bundle. On the next poll it sends that value back as `If-None-Match`. Two outcomes: - **`304 Not Modified`** — the service is telling OPA the resource is byte-identical to what the Etag names. OPA transfers no body, does not touch the compiler, does not activate, and the active revision is unchanged. The poll costs one round trip. - **`200 OK` with a new Etag** — OPA downloads the tarball, parses and compiles the Rego, writes data into the store under the bundle's roots, and swaps the new policy in. Only after a successful activation does the reported active revision change. The download is the visible cost, but **activation is the expensive one**. Compiling a large policy and writing a large data document is CPU and memory work, and it happens on every activation. An agent that activates identical content every twenty seconds is doing that work for nothing. This is a real and common misconfiguration: a bundle served from an object store or a static file server that does not emit `Etag` (or a proxy that strips it) turns every poll into a full download and a full activation. The symptom is a CPU floor on every OPA in the fleet that scales with policy size and drops the moment conditional requests start working. Nothing breaks — decisions stay correct — which is exactly why it survives for months. ## Delta bundles Conditional polling handles "nothing changed". Delta bundles handle the opposite problem: something changed, but resending the whole snapshot is wasteful. A delta bundle carries a patch — a list of upsert and remove operations against paths in the document tree — rather than a full tree, so a large context data document can be kept fresh with small transfers. Two constraints matter: - **A delta bundle can only change data, not policy.** Rego changes require a full snapshot bundle. - **A delta is meaningless without the snapshot it patches.** An OPA that restarts has an empty store, so the bundle service must serve it a full snapshot before deltas make sense again. ## What happens when the service is unavailable A failed poll is not fatal and does not clear anything. OPA logs the error, the status plugin records it, and the previously activated bundle keeps answering queries. That is the deliberate design — an unreachable bundle service should not turn every decision into an error — but it means "my policy is up to date" is never something a successful decision proves. The one place this changes shape is a **restart**. A fresh OPA has no policy at all, so if the bundle service is down when it starts, it has nothing to activate. Enabling bundle persistence tells OPA to write each activated bundle to a directory on disk and load it at startup when the service cannot be reached, so a restart during a bundle-service outage comes up with the last known-good policy instead of an empty store. Without persistence, a restart in that window produces an OPA that is running, is answering, and has no rules — and a query against a decision path that does not exist is **undefined**, which for a gate written as "deny if any deny rule fires" reads as an empty deny set and therefore an allow. ## What to say in an interview Name the three mechanisms and what each one saves: `Etag`/`If-None-Match` with `304` avoids both transfer and activation when nothing changed; delta bundles avoid retransmitting an unchanged bulk of data when part of it changed; persistence avoids a cold start with no policy. Then say the part interviewers are listening for: none of them make a stalled bundle visible — that is the status plugin's job.
- What can a delta bundle carry, and what can it not?A delta bundle carries a patch against `data` — upsert and remove operations on paths — so a large data document can be updated without resending it. It cannot carry policy: Rego changes need a full snapshot bundle. It also cannot stand alone, because a patch assumes the snapshot it applies to, so a freshly restarted OPA with an empty store has to be given a snapshot first.
- Your bundle service is behind a proxy that strips Etag. What do you observe?Every poll returns 200 with a body, so OPA downloads and re-activates identical content on every interval. Decisions stay correct, so nothing alerts; what you see is a permanent CPU floor across the fleet from repeated parse, compile and store writes, scaling with policy and data size. Fixing the proxy or serving a stable Etag removes it immediately.
- OPA restarts while the bundle service is unreachable. What is it serving?Nothing, unless bundle persistence is enabled. A fresh process has an empty store, so there is no policy to evaluate and the configured decision path is undefined — which a deny-set style gate reads as no denials, an allow. With persistence, OPA writes each activated bundle to disk and loads that copy at startup when the service cannot be reached, so it comes up on the last known-good policy.
saying these in an interview costs you the question
- Thinks 304 still triggers a re-activation
- Says a failed poll clears OPA's loaded policy
- Believes delta bundles can update Rego policy
- Ignores that activation, not download, costs most
- Assumes a restart reloads policy from disk by default