Skip to content

fix(controller): pre-filter BindDefinitions by RoleBindings in namespace event fan-out#276

Open
MaxRink wants to merge 4 commits intotelekom:mainfrom
MaxRink:fix/namespace-event-fanout
Open

fix(controller): pre-filter BindDefinitions by RoleBindings in namespace event fan-out#276
MaxRink wants to merge 4 commits intotelekom:mainfrom
MaxRink:fix/namespace-event-fanout

Conversation

@MaxRink
Copy link
Copy Markdown
Collaborator

@MaxRink MaxRink commented Apr 15, 2026

Summary

namespaceToBindDefinitionRequests listed all BindDefinitions on every Namespace event, then performed in-memory selector matching to skip cluster-only ones. This meant O(N) cache reads and filter work for every namespace create/update/delete, where N is the total number of BindDefinitions in the cluster — including those with only clusterRoleBindings that can never be triggered by a namespace event.

Root Cause

The client.List call had no field selector, so the cache returned every BindDefinition regardless of whether it had any roleBindings. The in-loop check len(bindDef.Spec.RoleBindings) == 0 was the only guard, applied after the full list was materialised.

Fix

  • Add BindDefinitionHasRoleBindingsField = ".spec.hasRoleBindings" field index to pkg/indexer (constants, extractor func BindDefinitionHasRoleBindingsFunc, and SetupIndexes registration).
  • Use client.MatchingFields{indexer.BindDefinitionHasRoleBindingsField: indexer.BindDefinitionHasRoleBindingsTrue} on the List call so the controller-runtime cache index is used instead of a full scan.
  • Remove the now-dead len(bindDef.Spec.RoleBindings) == 0 branch from the in-loop filter.

The index lookup is in-process O(1) against the informer cache — no API server calls.

Impact

  • Clusters with many cluster-only BindDefinitions see significantly reduced per-namespace-event work.
  • No behaviour change for BindDefinitions with roleBindings — they are still enqueued exactly as before.
  • The pkg/indexer index is registered alongside the existing BindDefinitionTargetNameField and WebhookAuthorizerHasNamespaceSelectorField indexes — same registration pattern.

Notes

…ace fan-out

namespaceToBindDefinitionRequests listed ALL BindDefinitions on every
Namespace event and ran in-memory selector matching for each, O(N) work
where N is total BindDefinitions. BindDefinitions with only
ClusterRoleBindings were fetched only to be skipped inside the loop.

Add BindDefinitionHasRoleBindingsField field index to pkg/indexer and use
client.MatchingFields to restrict the List to only BindDefinitions with
at least one RoleBinding, eliminating cluster-only BDs from the fan-out
scan.
@MaxRink MaxRink requested a review from a team as a code owner April 15, 2026 12:12
Copilot AI review requested due to automatic review settings April 15, 2026 12:12
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR optimizes namespace event fan-out in the BindDefinition controller by using a controller-runtime cache field index to pre-filter BindDefinitions that can actually be affected by namespace changes (i.e., those with namespace-scoped roleBindings).

Changes:

  • Add a new cache field index (BindDefinitionHasRoleBindingsField) to efficiently identify BindDefinitions with at least one spec.roleBindings entry.
  • Update namespaceToBindDefinitionRequests to List BindDefinitions using client.MatchingFields against the new index, avoiding full scans.
  • Remove the now-dead per-item “cluster-only” skip branch inside the in-memory filtering loop.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
pkg/indexer/indexer.go Adds and registers the new BindDefinition “has roleBindings” field index + extractor function.
internal/controller/authorization/binddefinition_controller.go Switches namespace fan-out listing to use the new field index, reducing per-event work.
Comments suppressed due to low confidence (1)

internal/controller/authorization/binddefinition_controller.go:203

  • namespaceToBindDefinitionRequests now uses client.MatchingFields with indexer.BindDefinitionHasRoleBindingsField. The controller-runtime fake client requires the corresponding index to be registered via fake.NewClientBuilder().WithIndex(...); otherwise List returns an error and this mapper returns no requests. The existing unit tests for this function build a fake client without this index, so they will start failing unless updated to register indexer.BindDefinitionHasRoleBindingsFunc for authorizationv1alpha1.BindDefinition{}.
	// List only BindDefinitions with at least one RoleBinding — cluster-only
	// BindDefinitions (no RoleBindings) never need reconciliation on namespace
	// events because they produce no namespace-scoped RBAC objects.
	bindDefList := &authorizationv1alpha1.BindDefinitionList{}
	err := r.client.List(ctx, bindDefList,
		client.MatchingFields{indexer.BindDefinitionHasRoleBindingsField: indexer.BindDefinitionHasRoleBindingsTrue},
	)
	if err != nil {
		logger.Error(err, "failed to list BindDefinition resources", "namespace", namespace.Name)
		return nil
	}

Comment thread pkg/indexer/indexer.go
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants