Caching
ResourceWatcher
The ResourceWatcher uses a FusionCache
instance to deduplicate watch events before forwarding them to the reconciler.
The cache key is always the resource's metadata.uid.
Which value is cached — and therefore which events are skipped — depends on the configured Reconcile Strategy.
ByGeneration (Default)
The cache stores the last observed metadata.generation (long) for each resource.
MODIFIED/ADDEDevent: the watcher reads the cached generation and compares it with the incoming one. If the incoming generation is not greater, the event is silently dropped. Label and annotation changes never incrementgeneration(they are.metadatachanges). Status updates do not incrementgenerationwhen the CRD has a status subresource enabled.DELETEDevent: the cache entry is removed and the event is always forwarded to the reconciler.- Entities with
DeletionTimestamp(finalizer processing): the generation check is bypassed because finalizer removals do not incrementgeneration. Without this bypass, the finalizer would never be called.
ByResourceVersion
The cache stores the last observed metadata.resourceVersion (string) for each resource.
MODIFIED/ADDEDevent: the watcher compares the cachedresourceVersionwith the incoming one. If they are equal (identical write already seen), the event is dropped. Because every API server write — spec, status, labels, annotations, finalizers — produces a newresourceVersion, this strategy triggers reconciliation for all changes.DELETEDevent: same asByGeneration— the cache entry is removed and the event is always forwarded.- Entities with
DeletionTimestamp: no special bypass is needed. Each finalizer removal is a real API write, so theresourceVersionchanges naturally and reconciliation fires.
Each strategy uses its own named FusionCache instance (see table below), so their L1/L2 configurations are fully independent.
Default Configuration: In-Memory (L1) Cache
By default, and without any extra configuration, KubeOps uses a simple in-memory cache.
-
Advantages:
- Requires zero configuration.
- Very fast, as all data is held in the operator pod's memory.
-
Disadvantages:
- The cache is volatile. If the pod restarts, all stored values are lost, leading to a full reconciliation of all observed resources on the next watch list.
Advanced Configuration: Distributed (L2) Cache
For robust use in production or HA environments, it could be essential to extend the cache with a distributed L2 cache and a backplane. This ensures that all operator instances share a consistent state. A common setup for this involves using Redis.
FusionCache and Named Cache Instances
KubeOps uses two named FusionCache instances — one per reconcile strategy:
| Strategy | Cache name constant | Value |
|---|---|---|
ByGeneration | CacheConstants.CacheNames.ResourceWatcher | "ResourceWatcher" |
ByResourceVersion | CacheConstants.CacheNames.ResourceWatcherByResourceVersion | "ResourceWatcher.ByResourceVersion" |
Only the cache corresponding to the configured strategy is used at runtime. Having separate named
instances allows you to configure them independently — for example, applying a Redis L2 backend
only to the ByResourceVersion cache, or using different entry TTLs.
Via OperatorSettings.ConfigureResourceWatcherEntityCache, an Action<IFusionCacheBuilder> is
provided that configures the active cache (whichever strategy is selected). To configure both
caches individually, register named FusionCache instances directly on the DI container using the
FusionCache builder API.
Example: L2 cache for the default ByGeneration strategy
builder
.Services
.AddKubernetesOperator(settings => settings
.WithName(OperatorName)
.WithResourceWatcherEntityCaching(cacheBuilder =>
cacheBuilder
.WithCacheKeyPrefix($"{CacheConstants.CacheNames.ResourceWatcher}:")
.WithSerializer(_ => new FusionCacheSystemTextJsonSerializer())
.WithRegisteredDistributedCache()
.WithDefaultEntryOptions(options =>
options.Duration = TimeSpan.MaxValue)));
Example: Independent L2 configuration for ByResourceVersion
When using ReconcileStrategy.ByResourceVersion, configure the "ResourceWatcher.ByResourceVersion"
named cache. Because resourceVersion values are short-lived strings, you may prefer a shorter TTL
or an in-memory-only setup here:
builder
.Services
.AddKubernetesOperator(settings => settings
.WithName(OperatorName)
.WithReconcileStrategy(ReconcileStrategy.ByResourceVersion));
// Keep the ByResourceVersion cache in-memory only (default) —
// no WithResourceWatcherEntityCaching needed.
// Optionally configure the named cache directly for advanced scenarios:
builder.Services.AddFusionCache(CacheConstants.CacheNames.ResourceWatcherByResourceVersion)
.WithDefaultEntryOptions(options => options.Duration = TimeSpan.FromMinutes(30));
For an overview of all FusionCache features, refer to the corresponding documentation:
https://github.com/ZiggyCreatures/FusionCache/blob/main/docs/CacheLevels.md