A cached classification saves a lookup and freezes yesterday's opinion into today's decision. The real comparison is not cache versus no cache: it is an observation, its freshness, and the response your site chose. Lose the boundary between those three and an optimisation starts making policy on its own. The repair is bookkeeping, not a better feed.

Three records with three different clocks

An observation is the field you received, the feed it came from and the dataset version behind it. Freshness is a property of the cache entry: when the lookup ran, when the entry stops being trusted, and whether the last refresh returned anything. The response is what your site does about it for this request, this account or this action. They are produced by different owners and they decay at different rates, which is why one row called blocked destroys the distinction.

RecordWho owns itHow long it should liveDamage when it is folded into another row
The observationthe feed's operatoruntil a newer dataset version replaces ita stale lookup becomes indistinguishable from a fresh one
Its freshnessthe applicationone request's tolerance for agea failed refresh hides behind a cache hit
The responsewhoever owns the site policyuntil that policy is revieweda feed outage turns into an indefinite block

Two clocks deserve particular care. The moment your lookup ran is not the moment the provider published the dataset, and an operator reading the row later needs to know which of the two each value came from. Set expiry and refresh from the feed's documented update model and from the cost of being wrong, never from a number borrowed from another site.

Keep the field, not a verdict

MaxMind's anonymous-IP documentation is one concrete example of a feed that separates anonymous VPN, hosting provider, public proxy and Tor exit into distinct fields. It also documents that an unregistered VPN subnet can surface under the hosting flag instead. Flattening that into one boolean throws away the only part an operator could later audit.

Neither direction of inference is safe. A hosting label does not establish that a tunnel was used, and the absence of a VPN flag does not establish that nothing sat between the client and your service. Those are two different claims, and a single boolean cannot express either of them.

Key the entry to the address you verified

Behind a reverse proxy, the application has to use the client-address handling the deployment approved. A forwarding header supplied by the caller must never be able to select somebody else's cached reputation.

That failure is both a correctness bug and a security one, because an attacker who controls the header can inherit a trusted entry or pin a victim to a bad one. Derive the key from the connection your own infrastructure terminated, and record which derivation was used. When several proxies sit in front, document the chain instead of trusting the first header that looks plausible. If the chain is ambiguous, log the derived key and the header you rejected, because the second is what an incident review will ask about.

No evidence is not a clean result

A timeout, a malformed response and an unavailable database each mean something different from a successful lookup that matched nothing. Represent them as separate outcomes, and decide in advance what the protected action does when there is no evidence at all. A read-only page and a signed-in account action can reasonably follow different rules there.

Keeping normal authentication, or asking for a supported verification, is usually a better answer than treating an outage as blanket permission or as a site-wide ban. Bound how long a lookup may run and stop refresh storms with whatever mechanism the integration supports. When a stale value is used during an outage, label it as stale at the point of decision, and make the refresh's last failure visible so a long-running fault cannot pass for a healthy entry. Whatever the choice, write it where the policy is enforced rather than in a comment beside the lookup.

Retention belongs to the same decision: keep the fields this function needs and the review trail behind them, nothing more. An IP cache does not have to become a browsing history.

Accept four cases, including a correction

Test a fresh result, an expired result, a failed lookup and a classification that was later corrected. Each exercises a different branch, and the last one is the only one that shows whether a mistaken restriction can be undone.

Then confirm that an ordinary user has a route to ask for review after a wrong block, because the feed will be wrong sometimes and the queue is part of the design. Write the four cases into the runbook with the policy owner's name beside them. The design earns its place when it removes redundant lookups while keeping uncertainty intact and the decision visibly owned by a person.

Lahatsoratra mifandraika