Skip to content

Cache namespace-filtered policy enforcers to fix CPU hotspot#2480

Merged
thjaeckle merged 2 commits into
eclipse-ditto:masterfrom
beyonnex-io:bugfix/policy-enforcer-namespace-cache-cpu-hotspot
Jul 3, 2026
Merged

Cache namespace-filtered policy enforcers to fix CPU hotspot#2480
thjaeckle merged 2 commits into
eclipse-ditto:masterfrom
beyonnex-io:bugfix/policy-enforcer-namespace-cache-cpu-hotspot

Conversation

@thjaeckle

Copy link
Copy Markdown
Member

PolicyEnforcer.forNamespace(ns) rebuilt the entire TreeBasedPolicyEnforcer on every enforced signal when a policy had namespace-scoped entries (introduced with #2325 in 3.9.0). A prod JFR profile of ditto-things showed this path (ThingEnforcerActor -> forNamespace -> defaultEvaluator -> createInstance) accounting for ~18% of JVM user CPU.

The base PolicyEnforcer is cached per policy and replaced wholesale on every policy update, so forNamespace(ns) is a pure function of the namespace for a given instance. Memoize its result in a per-instance Caffeine cache so the enforcer tree is built at most once per distinct namespace instead of per signal. The memo is invalidated naturally when the instance is replaced. The "return this" fast-path (no entries filtered) is preserved.

forNamespace is only ever called by the things-service enforcer on the long-lived, provider-cached instances (from PolicyEnforcerCacheLoader), so only those get a bounded cache whose size is operator-configurable via ditto.policies-enforcer-cache.namespace-filtered-enforcer-max-size (default 100, env DITTO_POLICIES_ENFORCER_NAMESPACE_FILTERED_MAX_SIZE). All other construction paths (of/embed/withResolvedImports, filtered children, and the policies-service enforcers) never accumulate in the cache and use an unbounded (always-empty) one, so no hard-coded default size is needed.

Helm values and the policies/things/connectivity deployment templates expose the new setting.

@thjaeckle thjaeckle added this to the 3.9.3 milestone Jul 2, 2026
@thjaeckle thjaeckle self-assigned this Jul 2, 2026
PolicyEnforcer.forNamespace(ns) rebuilt the entire TreeBasedPolicyEnforcer
on every enforced signal when a policy had namespace-scoped entries
(introduced with eclipse-ditto#2325 in 3.9.0). A prod JFR profile of ditto-things showed
this path (ThingEnforcerActor -> forNamespace -> defaultEvaluator ->
createInstance) accounting for ~18% of JVM user CPU.

The base PolicyEnforcer is cached per policy and replaced wholesale on every
policy update, so forNamespace(ns) is a pure function of the namespace for a
given instance. Memoize its result in a per-instance Caffeine cache so the
enforcer tree is built at most once per distinct namespace instead of per
signal. The memo is invalidated naturally when the instance is replaced. The
"return this" fast-path (no entries filtered) is preserved.

forNamespace is only ever called by the things-service enforcer on the
long-lived, provider-cached instances (from PolicyEnforcerCacheLoader), so
only those get a bounded cache whose size is operator-configurable via
ditto.policies-enforcer-cache.namespace-filtered-enforcer-max-size (default
100, env DITTO_POLICIES_ENFORCER_NAMESPACE_FILTERED_MAX_SIZE). All other
construction paths (of/embed/withResolvedImports, filtered children, and the
policies-service enforcers) never accumulate in the cache and use an
unbounded (always-empty) one, so no hard-coded default size is needed.

Helm values and the policies/things/connectivity deployment templates expose
the new setting.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@thjaeckle thjaeckle force-pushed the bugfix/policy-enforcer-namespace-cache-cpu-hotspot branch from bbf9050 to 5141d85 Compare July 2, 2026 11:49
@thjaeckle thjaeckle requested a review from hu-ahmed July 2, 2026 11:52
@thjaeckle thjaeckle added the bug label Jul 2, 2026
@thjaeckle

Copy link
Copy Markdown
Member Author

@hu-ahmed hu-ahmed left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!
just small minor stuff

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@thjaeckle thjaeckle merged commit 4fa7167 into eclipse-ditto:master Jul 3, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants