Skip to content

Commit

Permalink
doc(kdp): update gctx-jmespath-cache
Browse files Browse the repository at this point in the history
Signed-off-by: Khaled Emara <[email protected]>
  • Loading branch information
KhaledEmaraDev committed Dec 19, 2024
1 parent 9dcf43d commit a129085
Showing 1 changed file with 66 additions and 71 deletions.
137 changes: 66 additions & 71 deletions proposals/gctx-jmespath-cache.md
Original file line number Diff line number Diff line change
@@ -1,54 +1,63 @@
# Meta

[meta]: #meta

- Name: Global Context Entry Caching Enhancements
- Start Date: 2024-12-17
- Update Date: 2024-12-19
- Author(s): @KhaledEmaraDev

# Table of Contents

[table-of-contents]: #table-of-contents

- [Meta](#meta)
- [Table of Contents](#table-of-contents)
- [Overview](#overview)
- [Definitions](#definitions)
- [Motivation](#motivation)
- [Proposal](#proposal)
- [Implementation](#implementation)
- [Migration (OPTIONAL)](#migration-optional)
- [Migration](#migration)
- [Drawbacks](#drawbacks)
- [Alternatives](#alternatives)
- [Prior Art](#prior-art)
- [Unresolved Questions](#unresolved-questions)
- [CRD Changes (OPTIONAL)](#crd-changes-optional)
- [CRD Changes](#crd-changes)

# Overview

[overview]: #overview

This proposal aims to enhance the performance of Kyverno's Global Context Entry feature by introducing caching mechanisms for JMESPath expressions. Currently, these expressions are evaluated on every lookup, which is expensive. This proposal explores two approaches: one that moves JMESPath evaluation to write time by storing it in the `GlobalContextEntry` CRD, and another approach using a cache keyed by the Global Context Entry name and the `jmesPath` expression. This document also introduces a `ttl` attribute for both solutions.
This proposal aims to enhance the performance of Kyverno's Global Context Entry feature by introducing caching mechanisms for JMESPath expressions. Currently, these expressions are evaluated on every lookup, which is expensive. This proposal explores one approach: where JMESPath evaluation is performed at write time by storing projections of data in the `GlobalContextEntry` CRD. A projection is like a materialized view in databases, where it does some pre-computation by applying the JMESPath expression and storing its output in a new alias that can later be accessed from within contexts in Policies.

# Definitions

[definitions]: #definitions

* **Global Context Entry:** A Kyverno CRD that caches Kubernetes resources or API call results.
* **JMESPath:** A query language for JSON data, used here to extract specific parts of the returned API data or resource.
* **TTL (Time To Live):** The duration for which data is considered valid in a cache before it is refreshed or invalidated.
* **Write Time:** The time when data is initially stored or cached. In the case of the Global Context Entry this is the time when the data from Kubernetes or the API Call is retrieved.
* **Lookup Time:** The time when data is retrieved from a cache or source.
- **Global Context Entry:** A Kyverno CRD that caches Kubernetes resources or API call results.
- **JMESPath:** A query language for JSON data, used here to extract specific parts of the returned API data or resource.
- **Write Time:** The time when data is initially stored or cached. In the case of the Global Context Entry this is the time when the data from Kubernetes or the API Call is retrieved.
- **Lookup Time:** The time when data is retrieved from a cache or source.
- **Projection**: A pre-computed and cached result of applying a JMESPath expression to the retrieved data within a GlobalContextEntry.

# Motivation

[motivation]: #motivation

- **Why should we do this?** Evaluating JMESPath expressions on every lookup is inefficient and can lead to performance bottlenecks, especially with complex expressions or frequent access. This impacts Kyverno's overall performance and scalability, particularly in larger clusters.
- **What use cases does it support?** This change will improve the performance of policies that utilize the Global Context Entry, especially those policies that use frequent lookups.
- **What is the expected outcome?** The expected outcome is improved policy performance due to less resource usage when making calls to the Global Context Entry.
- **Why should we do this?** Evaluating JMESPath expressions on every lookup is inefficient and can lead to performance bottlenecks, especially with complex expressions or frequent access. This impacts Kyverno's overall performance and scalability, particularly in larger clusters.
- **What use cases does it support?** This change will improve the performance of policies that utilize the Global Context Entry, especially those policies that use frequent lookups.
- **What is the expected outcome?** The expected outcome is improved policy performance due to less resource usage when making calls to the Global Context Entry.

# Proposal

[proposal]: #proposal

This proposal details two potential caching mechanisms for the Global Context Entry feature. Both mechanisms aim to improve performance by reducing the frequency of JMESPath evaluations. Additionally, they propose the addition of a `ttl` attribute.
This proposal details a caching mechanism for the Global Context Entry feature. It aims to improve performance by reducing the frequency of JMESPath evaluations. This is achieved by performing JMESPath evaluation at write time and storing the results as a projection within the `GlobalContextEntry`.

**Mechanism 1: JMESPath Evaluation at Write Time**
**JMESPath Evaluation at Write Time with Projections**

In this approach, the `jmesPath` expression is moved from the `globalReference` context definition to the `GlobalContextEntry` CRD definition under either the `kubernetesResource` or the `apiCall` spec. This means that when the GlobalContextEntry data is fetched and stored, the `jmesPath` expression is applied **once** and the resulting data is cached.
In this approach, the `jmesPath` expression is moved from the `globalReference` context definition to the `GlobalContextEntry` CRD definition, as part of a new `projections` field. This means that when the GlobalContextEntry data is fetched and stored, the `jmesPath` expressions within each projection are applied **once** and the resulting data is cached under a specified alias.

```yaml
apiVersion: kyverno.io/v2alpha1
Expand All @@ -61,10 +70,15 @@ spec:
version: v1
resource: deployments
namespace: fitness
jmesPath: length(@) # <== JMESPath moved here
ttl: 60s # <== Added TTL attribute
projections:
- name: length_of_deployments
jmesPath: length(@)
- name: first_deployment_name
jmesPath: "[0].metadata.name"
```
or
```yaml
apiVersion: kyverno.io/v2alpha1
kind: GlobalContextEntry
Expand All @@ -74,97 +88,78 @@ spec:
apiCall:
urlPath: "/apis/apps/v1/namespaces/fitness/deployments?labelSelector=app=blue"
refreshInterval: 10s
jmesPath: length(@) # <== JMESPath moved here
ttl: 60s # <== Added TTL attribute
projections:
- name: length_of_deployments
jmesPath: length(@)
```
The `globalReference` definition in the policy context would now only need the name of the GlobalContextEntry.
The `globalReference` definition in the policy context would now only need the name of the GlobalContextEntry and a `projection` field specifying which projection to use.

```yaml
context:
- name: deployments
- name: deployment_count
globalReference:
name: deployments
```

**Mechanism 2: Caching with Combined Key**

In this approach, we retain the current design of applying the `jmesPath` on lookup and introduce a cache. The cache's key is a combination of the GlobalContextEntry's `name` and the `jmesPath` expression. The cache's value is the result of applying the `jmesPath` expression.

```yaml
context:
- name: deployments
projection: length_of_deployments
- name: first_deployment
globalReference:
name: deployments
jmesPath: length(@)
ttl: 60s # <== TTL attribute added to globalReference.
projection: first_deployment_name
```
With this approach, we are now caching the result of `length(@)` for GlobalContextEntry `deployments`. When this entry is requested with a different `jmesPath`, then the cache will be a miss and will re-evaluate.

**TTL Attribute**

For both mechanisms, a Time-To-Live (TTL) attribute is introduced. This can either be added under the `GlobalContextEntry` or under `globalReference`, depending on the approach. This attribute specifies the duration for which the cached data is considered valid. After the TTL expires, the cached data is refreshed from Kubernetes or the API endpoint. For the first approach, the `ttl` is applied to the whole Global Context Entry. For the second approach, it's on the specific cached entry that's a result of that `jmesPath` being applied.

**User Experience**

* **Mechanism 1 (JMESPath at Write Time):** For existing users of the Global Context Entry, this change will involve moving the `jmesPath` expression from policy context to the `GlobalContextEntry`. It simplifies the policy, as it only requires the GlobalContextEntry name and it's less verbose. New users will learn to configure the `jmesPath` within the GlobalContextEntry from the start.
* **Mechanism 2 (Combined Key Cache):** For existing users, this is largely transparent. They might have to specify a `ttl` if they wish to. New users will learn to use the `ttl` within the context definition.
For existing users of the Global Context Entry, this change will involve moving the `jmesPath` expression from policy context to the `GlobalContextEntry` under the `projections` field. It simplifies the policy, as it only requires the GlobalContextEntry name and the projection name, and it's less verbose. New users will learn to configure the `jmesPath` within the GlobalContextEntry from the start. Multiple projections on the same GlobalContextEntry are now possible.

# Implementation
[implementation]: #implementation

**Mechanism 1 (JMESPath at Write Time):**

1. **CRD Modification:** Modify the `GlobalContextEntry` CRD to include a `jmesPath` field. A `ttl` field would be added to the top-level of the CRD spec.
2. **Data Retrieval:** When fetching data from Kubernetes or an API endpoint, the specified `jmesPath` expression is immediately applied to the retrieved data before caching it.
3. **Cache Storage:** The result of the `jmesPath` evaluation is stored in the cache, keyed by the Global Context Entry name.
4. **Policy Context Retrieval:** When the `globalReference` is used, the cached result is returned.
5. **TTL Management:** The cache entry will be invalidated and updated if the `ttl` has expired.

**Mechanism 2 (Combined Key Cache):**
[implementation]: #implementation

1. **Cache Implementation:** Implement a cache that stores the result of `jmesPath` evaluations. This cache will be keyed by `GlobalContextEntry.name` and the `jmesPath` expression used to evaluate the data.
2. **Data Retrieval:** When a `globalReference` is used, the cache is checked for an existing entry using the GlobalContextEntry's `name` and the `jmesPath` provided.
3. **Cache Hit:** If a cache hit is found and the entry is not expired, return the cached value.
4. **Cache Miss:** If a cache miss occurs, fetch the data from Kubernetes or an API endpoint, evaluate the `jmesPath`, store the result in the cache with a `ttl` configured.
5. **TTL Management**: The cache entry will be invalidated and updated if the `ttl` has expired.
1. **CRD Modification:** Modify the `GlobalContextEntry` CRD to include a `projections` field. Each entry in the `projections` field will include a `name` and a `jmesPath`.
2. **Data Retrieval:** When fetching data from Kubernetes or an API endpoint, the specified `jmesPath` expressions within the `projections` field are immediately applied to the retrieved data before caching it.
3. **Cache Storage:** The result of each `jmesPath` evaluation is stored in the cache, keyed by the Global Context Entry name and the projection name.
4. **Policy Context Retrieval:** When the `globalReference` is used, the cached result corresponding to the specified projection is returned.
5. **Data Refresh:** All projections for a `GlobalContextEntry` will be re-evaluated and the cache updated whenever the underlying data for the `GlobalContextEntry` changes.

**General Implementation Notes:**

* The cache implementation can use an in-memory cache with a cleanup process based on the configured TTL.
* Error handling for invalid `jmesPath` expressions should be added, and these errors should be reported to the user via the Kyverno events and in the logs.
* Metrics must be updated to track cache hit rate.
- The cache implementation can use an in-memory cache.
- Error handling for invalid `jmesPath` expressions should be added, and these errors should be reported to the user via the Kyverno events and in the logs. If a `jmesPath` fails to evaluate, it will not be cached and the policy will fail with a specific error message indicating the projection could not be evaluated.

# Migration
[migration-optional]: #migration-optional

**Mechanism 1:** Requires changes to existing GlobalContextEntries. It is not transparent and will require user intervention. Users will need to move the `jmesPath` to the GlobalContextEntry and remove it from the policy context. This approach would also require a new Kyverno release to update the CRD to reflect this change.
**Mechanism 2:** Is largely transparent as a new `ttl` is added to the globalReference which is optional and will not affect existing users.
[migration]: #migration

Requires changes to existing GlobalContextEntries and policies. It is not transparent and will require user intervention. Users will need to move the `jmesPath` to the GlobalContextEntry under the `projections` field, giving it a name, and then change the policy to reference the projection by name. This approach would also require a new Kyverno release to update the CRD to reflect this change.

# Drawbacks

[drawbacks]: #drawbacks

**Mechanism 1:**
* The primary drawback is that it is less flexible. Each `GlobalContextEntry` can only hold one specific view of the data, determined by the `jmesPath`. If a policy needs a different `jmesPath` expression on the same data, a new `GlobalContextEntry` must be created.
* Requires user intervention and is not backwards compatible.
- The primary drawback is that it is less flexible than specifying the jmespath in the policy. Each projection in a `GlobalContextEntry` is a specific view of the data, determined by the `jmesPath`. If a policy needs a different `jmesPath` expression on the same data, a new projection must be added to the `GlobalContextEntry`. However, this is still an improvement, as many policies can then share the same projection.
- Requires user intervention and is not backwards compatible.

# Alternatives

**Mechanism 2:**
* Adds complexity to the caching mechanisms.
[alternatives]: #alternatives

- **JMESPath Evaluation with TTL**: We could evaluate the `jmesPath` on every lookup but store the result in a cache with a `ttl`. The cache's key is a combination of the GlobalContextEntry's `name` and the `jmesPath` expression. The cache's value is the result of applying the `jmesPath` expression. This is more flexible but does not perform as well as the proposed approach.

# Prior Art

[prior-art]: #prior-art

* Kyverno already uses caching for Kubernetes resource lookups. This proposal extends that by integrating JMESPath results within the cache system.
* Other systems like data pipelines or caching services often employ similar strategies of evaluating expressions and results and storing with an optional TTL.
- Kyverno already uses caching for Kubernetes resource lookups. This proposal extends that by integrating JMESPath results within the cache system.
- Other systems like data pipelines or caching services often employ similar strategies of evaluating expressions and results.

# Unresolved Questions

[unresolved-questions]: #unresolved-questions

* Should we have an additional global cache for cached data? It's currently per-controller, does this work?
- Should we have an additional global cache for cached data? It's currently per-controller, does this work?

# CRD Changes (OPTIONAL)
[crd-changes-optional]: #crd-changes-optional
# CRD Changes

**Mechanism 1:** Requires a change to the GlobalContextEntry CRD and a new Kyverno release.
[crd-changes]: #crd-changes

**Mechanism 2:** No CRD changes are required but a new field is added to the policy CRD.
Requires a change to the GlobalContextEntry CRD and a new Kyverno release. A new `projections` field will be added to the spec.

0 comments on commit a129085

Please sign in to comment.