Skip to content

Commit 695cbd2

Browse files
committed
docs: read-cache-after-write consistency and event filtering (#3193)
Signed-off-by: Attila Mészáros <a_meszaros@apple.com>
1 parent cbfa58a commit 695cbd2

File tree

3 files changed

+105
-13
lines changed

3 files changed

+105
-13
lines changed

docs/content/en/blog/news/primary-cache-for-next-recon.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,15 @@ author: >-
55
[Attila Mészáros](https://github.com/csviri) and [Chris Laprun](https://github.com/metacosm)
66
---
77

8+
{{% alert title="Deprecated" %}}
9+
10+
Read-cache-after-write consistency feature replaces this functionality. (since version 5.3.0)
11+
12+
> It provides this functionality also for secondary resources and optimistic locking
13+
is not required anymore. See [details here](./../../docs/documentation/reconciler.md#read-cache-after-write-consistency-and-event-filtering).
14+
{{% /alert %}}
15+
16+
817
We recently released v5.1 of Java Operator SDK (JOSDK). One of the highlights of this release is related to a topic of
918
so-called
1019
[allocated values](https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#representing-allocated-values

docs/content/en/docs/documentation/reconciler.md

Lines changed: 90 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,84 @@ annotation. If you do not specify a finalizer name, one will be automatically ge
160160

161161
From v5, by default, the finalizer is added using Server Side Apply. See also `UpdateControl` in docs.
162162

163-
### Making sure the primary resource is up to date for the next reconciliation
163+
### Read-cache-after-write consistency and event filtering
164+
165+
It is an inherent issue with Informers that their caches are eventually consistent even
166+
with the updates to Kubernetes API done from the controller. From version 5.3.0 the framework
167+
supports stronger guarantees, both for primary and secondary resources. If this feature is used:
168+
169+
1. Reading from the cache after our update — even within the same reconciliation — returns a fresh resource.
170+
"Fresh" means at least the version of the resource that is in the response from our update,
171+
or a more recent version if some other party updated the resource after our update. In particular, this means that
172+
you can safely store state (e.g. generated IDs) in the status sub-resource of your resources since it is now
173+
guaranteed that the stored values will be observable during the next reconciliation.
174+
2. Filtering events for our updates. When a controller updates a resource an event is produced by the Kubernetes API and
175+
propagated to Informer. This would normally trigger another reconciliation. This is, however, not optimal since we
176+
already have that up-to-date resource in the current reconciliation cache. There is generally no need to reconcile
177+
that resource again. This feature also makes sure that the reconciliation is not triggered from the event from our
178+
writes.
179+
180+
181+
In order to benefit from these stronger guarantees, use [`ResourceOperations`](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ResourceOperations.java)
182+
from the context of the reconciliation:
183+
184+
```java
185+
186+
public UpdateControl<WebPage> reconcile(WebPage webPage, Context<WebPage> context) {
187+
188+
ConfigMap managedConfigMap = prepareConfigMap(webPage);
189+
// filtering and caching update
190+
context.resourceOperations().serverSideApply(managedConfigMap);
191+
192+
// fresh resource instantly available from our update in the cache
193+
var upToDateResource = context.getSecondaryResource(ConfigMap.class);
194+
195+
makeStatusChanges(webPage);
196+
197+
// built in update methods by default use this feature
198+
return UpdateControl.patchStatus(webPage);
199+
}
200+
```
201+
202+
`UpdateControl` and `ErrorStatusUpdateControl` by default use this functionality, but you can also update your primary resource at any time during the reconciliation using `ResourceOperations`:
203+
204+
```java
205+
206+
public UpdateControl<WebPage> reconcile(WebPage webPage, Context<WebPage> context) {
207+
208+
makeStatusChanges(webPage);
209+
// this is equivalent to UpdateControl.patchStatus(webpage)
210+
context.resourceOperations().serverSideApplyPrimaryStatus(webPage);
211+
return UpdateControl.noUpdate();
212+
}
213+
```
214+
215+
If your reconciler is built around the assumption that new reconciliations would occur after its own updates, a new
216+
`reschedule` method is provided on `UpdateControl` to immediately reschedule a new reconciliation, to mimic the previous
217+
behavior.
218+
219+
### Caveats
220+
221+
- This feature is implemented on top of the Fabric8 client informers, using additional caches in `InformerEventSource`,
222+
so it is safe to use `context.getSecondaryResources(..)` or `InformerEventSource.get(ResourceID)`methods. Listing
223+
resources directly via `InformerEventSource.list(..)`, however, won't work since this method directly reads from the
224+
underlying informer cache, thus bypassing the additional caches that make the feature possible.
225+
226+
227+
### Notes
228+
- This [talk](https://www.youtube.com/watch?v=HrwHh5Yh6AM&t=1387s) mentions this feature.
229+
- [Umbrella issue](https://github.com/operator-framework/java-operator-sdk/issues/2944) on GitHub.
230+
- We were able to implement this feature since Kubernetes introduces guideline to compare
231+
resource versions. See the details [here](https://github.com/kubernetes/enhancements/tree/master/keps/sig-api-machinery/5504-comparable-resource-version).
232+
233+
### Making sure the primary resource is up to date for the next reconciliation (deprecated)
234+
235+
{{% alert title="Deprecated" %}}
236+
237+
Read-cache-after-write consistency feature replaces this functionality.
238+
239+
> It provides this functionality also for secondary resources and optimistic locking is not required anymore. See details above.
240+
{{% /alert %}}
164241

165242
It is typical to want to update the status subresource with the information that is available during the reconciliation.
166243
This is sometimes referred to as the last observed state. When the primary resource is updated, though, the framework
@@ -263,30 +340,30 @@ See also [sample](https://github.com/operator-framework/java-operator-sdk/blob/m
263340
### Expectations
264341

265342
Expectations are a pattern to ensure that, during reconciliation, your secondary resources are in a certain state.
266-
For a more detailed explanation see [this blogpost](https://ahmet.im/blog/controller-pitfalls/#expectations-pattern).
267-
You can find framework support for this pattern in [`io.javaoperatorsdk.operator.processing.expectation`](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/expectation/)
268-
package. See also related [integration test](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/expectation/ExpectationReconciler.java).
269-
Note that this feature is marked as `@Experimental`, since based on feedback the API might be improved / changed, but we intend
270-
to support it, later also might be integrated to Dependent Resources and/or Workflows.
343+
For a more detailed explanation, see [this blogpost](https://ahmet.im/blog/controller-pitfalls/#expectations-pattern).
344+
You can find framework support for this pattern in the [`io.javaoperatorsdk.operator.processing.expectation`](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/expectation/)
345+
package. See also the related [integration test](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/expectation/onallevent/ExpectationReconciler.java).
346+
Note that this feature is marked as `@Experimental`: based on feedback the API may be improved or changed, but we intend
347+
to keep supporting it and may later integrate it into Dependent Resources and/or Workflows.
271348

272-
The idea is the nutshell, is that you can track your expectations in the expectation manager in the reconciler
273-
which has an API that covers the common use cases.
349+
The idea, in a nutshell, is that you can track your expectations in the expectation manager within the reconciler,
350+
which provides an API covering the common use cases.
274351

275-
The following sample is the simplified version of the integration test that implements the logic that creates a
276-
deployment and sets status message if there are the target three replicas ready:
352+
The following is a simplified version of the integration test that implements the logic to create a
353+
deployment and set a status message once the target three replicas are ready:
277354

278355
```java
279356
public class ExpectationReconciler implements Reconciler<ExpectationCustomResource> {
280357

281358
// some code is omitted
282-
359+
283360
private final ExpectationManager<ExpectationCustomResource> expectationManager =
284361
new ExpectationManager<>();
285362

286363
@Override
287364
public UpdateControl<ExpectationCustomResource> reconcile(
288365
ExpectationCustomResource primary, Context<ExpectationCustomResource> context) {
289-
// exiting asap if there is an expectation that is not timed out neither fulfilled yet
366+
// exit early if there is an expectation that has not yet timed out or been fulfilled
290367
if (expectationManager.ongoingExpectationPresent(primary, context)) {
291368
return UpdateControl.noUpdate();
292369
}
@@ -299,7 +376,7 @@ public class ExpectationReconciler implements Reconciler<ExpectationCustomResour
299376
return UpdateControl.noUpdate();
300377
} else {
301378
// Checks the expectation and removes it once it is fulfilled.
302-
// In your logic you might add a next expectation based on your workflow.
379+
// In your logic, you might add a next expectation based on your workflow.
303380
// Expectations have a name, so you can easily distinguish multiple expectations.
304381
var res = expectationManager.checkExpectation("deploymentReadyExpectation", primary, context);
305382
if (res.isFulfilled()) {

docs/content/en/docs/documentation/working-with-es-caches.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,3 +216,9 @@ With this index in place, you can retrieve the target resources very efficiently
216216
.collect(Collectors.toSet()))
217217
.withNamespacesInheritedFromController().build(), context);
218218
```
219+
220+
## Read-cache-after-write consistency and event filtering
221+
222+
From version 5.3.0 we provide stronger consistency guarantees and
223+
other features on top of basic informers, see [this section](reconciler.md#read-cache-after-write-consistency-and-event-filtering)
224+
for details.

0 commit comments

Comments
 (0)