Skip to content

Commit c39b349

Browse files
Mohit Guptaclaude
andcommitted
improve: cache-aware secondary resource lookup for KubernetesDependentResource
Introduces a direct O(1) cache lookup path in KubernetesDependentResource to retrieve the secondary resource from the InformerEventSource cache, bypassing the costlier Context.getSecondaryResources() path. ## Problem AbstractDependentResource.getSecondaryResource() calls Context.getSecondaryResources(resourceType()), which iterates the InformerEventSource index to build a Set<R> of all secondary resources for the primary, then delegates to selectTargetSecondaryResource() to filter down to the single expected resource. That implementation calls getOrComputeDesired() to derive the target name/namespace, and then streams and filters the full Set — even though InformerEventSource already provides an O(1) cache.get(ResourceID) lookup. ## Solution KubernetesDependentResource now overrides getSecondaryResource() to call InformerEventSource.get(targetSecondaryResourceID) directly — a single hash-map lookup — when the event source is present. Falls back to the standard AbstractDependentResource path (via selectTargetSecondaryResource) when the event source is not directly available (e.g. shared event source scenario). The existing targetSecondaryResourceID() method drives both the new fast path and the existing selectTargetSecondaryResource() fallback. Override it to return a cheap, statically-derivable ResourceID (e.g. from the primary name/namespace) to avoid the getOrComputeDesired() call during secondary resource lookup entirely. The optimisation is intentionally scoped to KubernetesDependentResource where R extends HasMetadata and ResourceID is a valid secondary resource identifier. The base class AbstractEventSourceHolderDependentResource is left unchanged because R is not bounded to HasMetadata there and ResourceID is not a universally applicable secondary resource key. ## Backward compatibility All changes are additive or override-with-fallback: - getSecondaryResource() falls back to super when eventSource() is empty. - selectTargetSecondaryResource() is retained as the fallback for the shared event source scenario. - targetSecondaryResourceID() behaviour is unchanged. Signed-off-by: Mohit Gupta <mohitg@zeta.tech> Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Mohit Gupta <mohitg@zeta.tech>
1 parent 04a694e commit c39b349

1 file changed

Lines changed: 54 additions & 5 deletions

File tree

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResource.java

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,48 @@ protected void addSecondaryToPrimaryMapperAnnotations(
247247
annotations.put(typeKey, GroupVersionKind.gvkFor(primary.getClass()).toGVKString());
248248
}
249249

250+
/**
251+
* Returns the secondary resource using a direct O(1) lookup against the {@link
252+
* InformerEventSource} cache when possible, bypassing the costlier {@link
253+
* io.javaoperatorsdk.operator.api.reconciler.Context#getSecondaryResources} path.
254+
*
255+
* <p>The fast path is taken when the event source is present and {@link
256+
* #targetSecondaryResourceID(HasMetadata, Context)} returns a non-null {@link ResourceID}. In
257+
* that case {@code informerEventSource.get(resourceID)} is called directly — a single hash-map
258+
* lookup — without building a {@code Set} of all secondary resources and filtering through it.
259+
*
260+
* <p>Falls back to the standard {@link
261+
* AbstractDependentResource#getSecondaryResource(HasMetadata, Context)} path (which delegates to
262+
* {@link #selectTargetSecondaryResource(Set, HasMetadata, Context)}) when the event source is not
263+
* present or {@code targetSecondaryResourceID} returns {@code null}.
264+
*
265+
* @param primary the primary resource
266+
* @param context the reconciliation context
267+
* @return the secondary resource if found, or {@link Optional#empty()}
268+
*/
269+
@Override
270+
public Optional<R> getSecondaryResource(P primary, Context<P> context) {
271+
return eventSource()
272+
.flatMap(
273+
es -> {
274+
ResourceID targetID = targetSecondaryResourceID(primary, context);
275+
if (targetID != null) {
276+
return es.get(targetID);
277+
}
278+
return super.getSecondaryResource(primary, context);
279+
});
280+
}
281+
282+
/**
283+
* Selects the target secondary resource from the full set of candidates. This is the fallback
284+
* path taken when {@link #getSecondaryResource(HasMetadata, Context)} cannot use the direct cache
285+
* lookup (e.g. when a shared event source is used and the event source reference is not directly
286+
* available via {@link #eventSource()}).
287+
*
288+
* <p>Prefer overriding {@link #targetSecondaryResourceID(HasMetadata, Context)} for
289+
* performance-critical paths to avoid both this filtering and the {@code getOrComputeDesired}
290+
* call it triggers.
291+
*/
250292
@Override
251293
protected Optional<R> selectTargetSecondaryResource(
252294
Set<R> secondaryResources, P primary, Context<P> context) {
@@ -262,12 +304,19 @@ protected Optional<R> selectTargetSecondaryResource(
262304
}
263305

264306
/**
265-
* Override this method in order to optimize and not compute the desired when selecting the target
266-
* secondary resource. Simply, a static ResourceID can be returned.
307+
* Returns the {@link ResourceID} of the target secondary resource. Used by both {@link
308+
* #getSecondaryResource(HasMetadata, Context)} (fast cache-lookup path) and {@link
309+
* #selectTargetSecondaryResource(Set, HasMetadata, Context)} (fallback filtering path).
310+
*
311+
* <p>Override this method to return a cheap, statically-derivable {@link ResourceID} (e.g.
312+
* derived directly from the primary's name and namespace) to avoid computing the full desired
313+
* state during secondary resource lookup. The default implementation calls {@link
314+
* #getOrComputeDesired(Context)}, which is correct but may be costlier when the desired state is
315+
* non-trivial to compute.
267316
*
268-
* @param primary resource
269-
* @param context of current reconciliation
270-
* @return id of the target managed resource
317+
* @param primary the primary resource
318+
* @param context the current reconciliation context
319+
* @return the {@link ResourceID} of the managed secondary resource
271320
*/
272321
protected ResourceID targetSecondaryResourceID(P primary, Context<P> context) {
273322
return ResourceID.fromResource(getOrComputeDesired(context));

0 commit comments

Comments
 (0)