Skip to content

feat: Specify caching for OFREP in server providers #17

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jan 22, 2025
Merged

Conversation

thomaspoignant
Copy link
Member

This PR

Why this feature?

With the current implementation of OFREP on the server side the provider has to do a remote call to an API for each evaluation which can add latency to the application.
This caching mechanism helps to reduce the overhead of calling the API each time.

The cacheable field is here for the flag management system to notify the provider that it is ok to cache this flag evaluation. We don't want to cache all evaluations because some of them contain time-based changes and in those cases, we accept calling multiple times the API because the evaluation result can change and the cache will not be reliable.
By setting the cacheable field we let the decision to the flag management system.

Sequence diagram

This sequence diagram explains how it can work from a provider's perspective.

sequenceDiagram
    participant c as Server Application
    participant p as OFREP Provider
    participant f as Flag Management<br/>System
    c ->> p: client.getBooleanValue('v2_enabled', false);
    p -->> p: retrieve combination of flag + context<br/>from the cache
    alt not available in the cache
        p ->> f: call /ofrep/v1/evaluate/flags/v2_enabled<br/>with evaluation context
        f -->> p: 200 + evaluation result
        p -->> p: If cacheable = true, store in a cache<br/>Key: combination of flag + context<br/>Value: evaluation result<br/>(Data will be evicted from the cache after the TTL<br/>OR<br/>If we get notify from a configuration change)
        p -->> c: evaluation result
    else available in the cache
        p -->> c: evaluation result (with CACHED reason)
    end
Loading

@thomaspoignant thomaspoignant requested a review from a team as a code owner May 15, 2024 10:14
type: boolean
description: set to true if you want the provider to cache the evaluation results
ttl:
Copy link
Member Author

Choose a reason for hiding this comment

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

Default TTL can be 0 and we will relay only on the polling to evict data from the cache.

Copy link
Contributor

Choose a reason for hiding this comment

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

Agree, by default the behaviour should be no-cache so every evaluation is done remotely.

Copy link
Member

@lukas-reining lukas-reining left a comment

Choose a reason for hiding this comment

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

I really like the idea and think we should implement server cache control.

I am wondering if we could not just use the Cache-Control header with the max-age directive here and leave the TTL out of the response body.
This feels more idiomatic to me and could be nice in cases where other HTTP tooling like proxies is in the loop.

@beeme1mr
Copy link
Member

How will the cache be busted when the configuration changes without SSE, WebSockets, or polling?

@thomaspoignant
Copy link
Member Author

How will the cache be busted when the configuration changes without SSE, WebSockets, or polling?

@beeme1mr I am not sure I understand what you mean here.
Are you talking about the cases where the flag configuration changes based on time or automated actions?

If yes for me those are the usecases when we should set cacheable to false, and the provider will continue to always do remote calls for this flag because we will never have any cache value for this specific flag.

@thomaspoignant
Copy link
Member Author

I really like the idea and think we should implement server cache control.

I am wondering if we could not just use the Cache-Control header with the max-age directive here and leave the TTL out of the response body. This feels more idiomatic to me and could be nice in cases where other HTTP tooling like proxies is in the loop.

@lukas-reining I like the idea but I have one doubt.
You are talking about proxies here that may serve the cache because of the Cache-Control headers but it can result in serving a wrong value because caching typically does not take into account the request body.

We will do POST requests to the same URL with different evaluation contexts in the body and have a risk that the proxy in the middle is serving an API response not linked to the context.

sequenceDiagram
    participant p as provider
    participant px as proxy
    participant f as flag management system

    Note over p,f: Evaluation for user 1
    p ->> px: POST /ofrep/v1/evaluate/flags/my_flag<br/>(targetingKey: user1)
    px ->> f: POST /ofrep/v1/evaluate/flags/my_flag<br/>(targetingKey: user1)
    f -->>px: response (user1)
    px -->> p: response (user1)

    Note over p,f: Evaluation for user 2
    p ->> px: POST /ofrep/v1/evaluate/flags/my_flag<br/>(targetingKey: user2)
    px-->>px: retrieve the response from the cache
    px -->> p: response (user1)
    Note over p,px: ⚠️ Most of the proxies are not using<br/>the body in their caching policies.

Loading

@lukas-reining
Copy link
Member

I really like the idea and think we should implement server cache control.
I am wondering if we could not just use the Cache-Control header with the max-age directive here and leave the TTL out of the response body. This feels more idiomatic to me and could be nice in cases where other HTTP tooling like proxies is in the loop.

@lukas-reining I like the idea but I have one doubt. You are talking about proxies here that may serve the cache because of the Cache-Control headers but it can result in serving a wrong value because caching typically does not take into account the request body.

We will do POST requests to the same URL with different evaluation contexts in the body and have a risk that the proxy in the middle is serving an API response not linked to the context.

sequenceDiagram
    participant p as provider
    participant px as proxy
    participant f as flag management system

    Note over p,f: Evaluation for user 1
    p ->> px: POST /ofrep/v1/evaluate/flags/my_flag<br/>(targetingKey: user1)
    px ->> f: POST /ofrep/v1/evaluate/flags/my_flag<br/>(targetingKey: user1)
    f -->>px: response (user1)
    px -->> p: response (user1)

    Note over p,f: Evaluation for user 2
    p ->> px: POST /ofrep/v1/evaluate/flags/my_flag<br/>(targetingKey: user2)
    px-->>px: retrieve the response from the cache
    px -->> p: response (user1)
    Note over p,px: ⚠️ Most of the proxies are not using<br/>the body in their caching policies.

Loading

Mh good point, I was missing the targeting key.
Yes you are right @thomaspoignant, this will not work :)

Copy link
Contributor

@Kavindu-Dodan Kavindu-Dodan left a comment

Choose a reason for hiding this comment

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

I have this doubt , otherwise this seems a good opt-in feature.

@Kavindu-Dodan
Copy link
Contributor

How will the cache be busted when the configuration changes without SSE, WebSockets, or polling?

@beeme1mr I am not sure I understand what you mean here. Are you talking about the cases where the flag configuration changes based on time or automated actions?

If yes for me those are the usecases when we should set cacheable to false, and the provider will continue to always do remote calls for this flag because we will never have any cache value for this specific flag.

I think what @beeme1mr meant is upstream (flag mgmt system) changes and how to invalidate OFREP provider cache when a change happen.

I think the cacheable should set an appropriate ttl so systems do not get overloaded with evaluation calls. And it should be small enough so that upstream changes are visible to end systems (ex:- UI). The exact value (10seconds vs 2 seconds) should be decided by the vendor.

Signed-off-by: Thomas Poignant <thomas.poignant@gofeatureflag.org>
Signed-off-by: Kavindu Dodanduwa <Kavindu-Dodan@users.noreply.github.com>
@Kavindu-Dodan
Copy link
Contributor

@thomaspoignant I think this we should get some more approvals and get this merged. Besides, the only change is that we decided the configuration endpoint to be an extension. So we will need to define a server provider guideline, where we could define the default behavior of caching (ex:- enable caching through OFREP provider constructor options)

@thepabloaguilar
Copy link

Hi guys, I have a question. What's essentially difference between featureCacheInvalidation and featureCaching?
Are we not talking about the same thing but with two different mechanisms?

I understand that featureCacheInvalidation is about the provider polling the system each minPollingIntervalMs time and featureCaching is about invalidating the cache locally and then fetching the new state (if any). The main difference would be the last one returning an error if for some reason the request fails to get the new state as the local state is not valid anymore.

This is not so clear at first glance and maybe we could improve the wording/explanation of both. Also I can't see both being enabled at the same time, it'd be great to have a featureCache object and a strategy field with oneOf option

@toddbaert
Copy link
Member

Hi guys, I have a question. What's essentially difference between featureCacheInvalidation and featureCaching? Are we not talking about the same thing but with two different mechanisms?

I understand that featureCacheInvalidation is about the provider polling the system each minPollingIntervalMs time and featureCaching is about invalidating the cache locally and then fetching the new state (if any). The main difference would be the last one returning an error if for some reason the request fails to get the new state as the local state is not valid anymore.

This is not so clear at first glance and maybe we could improve the wording/explanation of both. Also I can't see both being enabled at the same time, it'd be great to have a featureCache object and a strategy field with oneOf option

It seems to me these are independent.

invalidation can be used to invalidate the cache, but the cache is useful on it's own just using a timer as well.

@toddbaert toddbaert self-requested a review January 22, 2025 19:52
@toddbaert toddbaert merged commit f84e632 into main Jan 22, 2025
3 checks passed
@toddbaert toddbaert deleted the server-cache branch January 22, 2025 19:53
thomaspoignant added a commit that referenced this pull request May 12, 2025
Signed-off-by: Thomas Poignant <thomas.poignant@gofeatureflag.org>
Signed-off-by: Kavindu Dodanduwa <Kavindu-Dodan@users.noreply.github.com>
Co-authored-by: Kavindu Dodanduwa <Kavindu-Dodan@users.noreply.github.com>
Co-authored-by: Todd Baert <todd.baert@dynatrace.com>
Signed-off-by: Thomas Poignant <thomas.poignant@gofeatureflag.org>
thomaspoignant added a commit that referenced this pull request May 16, 2025
Signed-off-by: Thomas Poignant <thomas.poignant@gofeatureflag.org>
Signed-off-by: Kavindu Dodanduwa <Kavindu-Dodan@users.noreply.github.com>
Co-authored-by: Kavindu Dodanduwa <Kavindu-Dodan@users.noreply.github.com>
Co-authored-by: Todd Baert <todd.baert@dynatrace.com>
Signed-off-by: Thomas Poignant <thomas.poignant@gofeatureflag.org>
thomaspoignant added a commit that referenced this pull request May 16, 2025
* feat: Client provider spec

Signed-off-by: Thomas Poignant <thomas.poignant@gofeatureflag.org>

* Update provider/specs/client.md

Co-authored-by: Mark Phelps <209477+markphelps@users.noreply.github.com>
Signed-off-by: Thomas Poignant <thomas.poignant@gmail.com>
Signed-off-by: Thomas Poignant <thomas.poignant@gofeatureflag.org>

* Update provider/specs/client.md

Co-authored-by: Mark Phelps <209477+markphelps@users.noreply.github.com>
Signed-off-by: Thomas Poignant <thomas.poignant@gmail.com>
Signed-off-by: Thomas Poignant <thomas.poignant@gofeatureflag.org>

* Update provider/specs/client.md

Co-authored-by: Michael Beemer <beeme1mr@users.noreply.github.com>
Signed-off-by: Thomas Poignant <thomas.poignant@gmail.com>
Signed-off-by: Thomas Poignant <thomas.poignant@gofeatureflag.org>

* Update provider/specs/client.md

Co-authored-by: Mark Phelps <209477+markphelps@users.noreply.github.com>
Signed-off-by: Thomas Poignant <thomas.poignant@gmail.com>
Signed-off-by: Thomas Poignant <thomas.poignant@gofeatureflag.org>

* Update provider/specs/client.md

Co-authored-by: Mark Phelps <209477+markphelps@users.noreply.github.com>
Signed-off-by: Thomas Poignant <thomas.poignant@gmail.com>
Signed-off-by: Thomas Poignant <thomas.poignant@gofeatureflag.org>

* Update provider/specs/client.md

Co-authored-by: Mark Phelps <209477+markphelps@users.noreply.github.com>
Signed-off-by: Thomas Poignant <thomas.poignant@gmail.com>
Signed-off-by: Thomas Poignant <thomas.poignant@gofeatureflag.org>

* Update provider/specs/client.md

Co-authored-by: Mark Phelps <209477+markphelps@users.noreply.github.com>
Signed-off-by: Thomas Poignant <thomas.poignant@gmail.com>
Signed-off-by: Thomas Poignant <thomas.poignant@gofeatureflag.org>

* Update provider/specs/client.md

Co-authored-by: Mark Phelps <209477+markphelps@users.noreply.github.com>
Signed-off-by: Thomas Poignant <thomas.poignant@gmail.com>
Signed-off-by: Thomas Poignant <thomas.poignant@gofeatureflag.org>

* Update provider/specs/client.md

Co-authored-by: Mark Phelps <209477+markphelps@users.noreply.github.com>
Signed-off-by: Thomas Poignant <thomas.poignant@gmail.com>
Signed-off-by: Thomas Poignant <thomas.poignant@gofeatureflag.org>

* Update provider/specs/client.md

Co-authored-by: Mark Phelps <209477+markphelps@users.noreply.github.com>
Signed-off-by: Thomas Poignant <thomas.poignant@gmail.com>
Signed-off-by: Thomas Poignant <thomas.poignant@gofeatureflag.org>

* Update provider/specs/client.md

Co-authored-by: Mark Phelps <209477+markphelps@users.noreply.github.com>
Signed-off-by: Thomas Poignant <thomas.poignant@gmail.com>
Signed-off-by: Thomas Poignant <thomas.poignant@gofeatureflag.org>

* Update provider/specs/client.md

Co-authored-by: Mark Phelps <209477+markphelps@users.noreply.github.com>
Signed-off-by: Thomas Poignant <thomas.poignant@gmail.com>
Signed-off-by: Thomas Poignant <thomas.poignant@gofeatureflag.org>

* Update provider/specs/client.md

Co-authored-by: Michael Beemer <beeme1mr@users.noreply.github.com>
Signed-off-by: Thomas Poignant <thomas.poignant@gmail.com>
Signed-off-by: Thomas Poignant <thomas.poignant@gofeatureflag.org>

* Update provider/specs/client.md

Co-authored-by: Michael Beemer <beeme1mr@users.noreply.github.com>
Signed-off-by: Thomas Poignant <thomas.poignant@gmail.com>
Signed-off-by: Thomas Poignant <thomas.poignant@gofeatureflag.org>

* update with review comments

Signed-off-by: Thomas Poignant <thomas.poignant@gofeatureflag.org>

* Move to guideline folder

Signed-off-by: Thomas Poignant <thomas.poignant@gofeatureflag.org>

* Replace specification

Signed-off-by: Thomas Poignant <thomas.poignant@gofeatureflag.org>

* add OpenAPI spec validator based on redocly cli (#15)

Signed-off-by: Kavindu Dodanduwa <kavindudodanduwa@gmail.com>
Signed-off-by: Thomas Poignant <thomas.poignant@gofeatureflag.org>

* chore(deps): update actions/checkout action to v4 (#16)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Signed-off-by: Thomas Poignant <thomas.poignant@gofeatureflag.org>

* Update guideline/static-context-provider.md

Co-authored-by: Michael Beemer <beeme1mr@users.noreply.github.com>
Signed-off-by: Thomas Poignant <thomas.poignant@gmail.com>
Signed-off-by: Thomas Poignant <thomas.poignant@gofeatureflag.org>

* Update guideline/static-context-provider.md

Co-authored-by: Lukas Reining <lukas.reining@codecentric.de>
Signed-off-by: Thomas Poignant <thomas.poignant@gmail.com>
Signed-off-by: Thomas Poignant <thomas.poignant@gofeatureflag.org>

* Update guideline/static-context-provider.md

Co-authored-by: Lukas Reining <lukas.reining@codecentric.de>
Signed-off-by: Thomas Poignant <thomas.poignant@gmail.com>
Signed-off-by: Thomas Poignant <thomas.poignant@gofeatureflag.org>

* update guidelines after removing configuration endpoint

Signed-off-by: Thomas Poignant <thomas.poignant@gofeatureflag.org>

* adding change context

Signed-off-by: Thomas Poignant <thomas.poignant@gofeatureflag.org>

* feat: Allow any reason (#20)

Signed-off-by: Thomas Poignant <thomas.poignant@gofeatureflag.org>

* feat!: change minPollingInterval field name to mention millisecond (#25)

* feat!: change minPollingInterval field name to mention millisecond

Signed-off-by: Thomas Poignant <thomas.poignant@gofeatureflag.org>

* change name to ms

Signed-off-by: Thomas Poignant <thomas.poignant@gofeatureflag.org>

---------

Signed-off-by: Thomas Poignant <thomas.poignant@gofeatureflag.org>

* feat: Group API in core and extensions (#23)

Signed-off-by: Thomas Poignant <thomas.poignant@gofeatureflag.org>

* feat!: Reverse the logic for supportedTypes (#24)

* feat!: Reverse the logic for supportedTypes

Signed-off-by: Thomas Poignant <thomas.poignant@gofeatureflag.org>

* fix

Signed-off-by: Thomas Poignant <thomas.poignant@gofeatureflag.org>

---------

Signed-off-by: Thomas Poignant <thomas.poignant@gofeatureflag.org>

* doc: adding providers link (#26)

Signed-off-by: Thomas Poignant <thomas.poignant@gofeatureflag.org>

* make flags property mandatory for bulk evaluation success response (#27)

Signed-off-by: Kavindu Dodanduwa <kavindudodanduwa@gmail.com>
Signed-off-by: Thomas Poignant <thomas.poignant@gofeatureflag.org>

* feat: Typo in header name (#28)

Signed-off-by: Thomas Poignant <thomas.poignant@gofeatureflag.org>

* add optional targeting key property (#30)

Signed-off-by: Michael Beemer <beeme1mr@users.noreply.github.com>
Signed-off-by: Thomas Poignant <thomas.poignant@gofeatureflag.org>

* fix: use correct header name for 429 bulk response (#32)

Signed-off-by: Roman Dmytrenko <rdmytrenko@gmail.com>
Signed-off-by: Thomas Poignant <thomas.poignant@gofeatureflag.org>

* feat: Specify caching for OFREP in server providers (#17)

Signed-off-by: Thomas Poignant <thomas.poignant@gofeatureflag.org>
Signed-off-by: Kavindu Dodanduwa <Kavindu-Dodan@users.noreply.github.com>
Co-authored-by: Kavindu Dodanduwa <Kavindu-Dodan@users.noreply.github.com>
Co-authored-by: Todd Baert <todd.baert@dynatrace.com>
Signed-off-by: Thomas Poignant <thomas.poignant@gofeatureflag.org>

* feat: add flag set metadata for bulk response and failures (#34)

Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
Signed-off-by: Thomas Poignant <thomas.poignant@gofeatureflag.org>

* Fixups: Add operationIds, remove invalid property, fix tag casing (#35)

Signed-off-by: Honza Dvorsky <honza@apple.com>
Signed-off-by: Thomas Poignant <thomas.poignant@gofeatureflag.org>

* ci: switch OpenAPI validators (#36)

Signed-off-by: Michael Beemer <beeme1mr@users.noreply.github.com>
Signed-off-by: Thomas Poignant <thomas.poignant@gofeatureflag.org>

* fix: address style issues

Signed-off-by: Thomas Poignant <thomas.poignant@gofeatureflag.org>

* Update static-context-provider.md

Co-authored-by: Lukas Reining <lukas.reining@codecentric.de>
Signed-off-by: Thomas Poignant <thomas.poignant@gmail.com>
Signed-off-by: Thomas Poignant <thomas.poignant@gofeatureflag.org>

* Update static-context-provider.md

Co-authored-by: Lukas Reining <lukas.reining@codecentric.de>
Signed-off-by: Thomas Poignant <thomas.poignant@gmail.com>
Signed-off-by: Thomas Poignant <thomas.poignant@gofeatureflag.org>

* adding timeout

Signed-off-by: Thomas Poignant <thomas.poignant@gofeatureflag.org>

* feat: Server provider guidelines (#42)

* feat: Server provider guideline

Signed-off-by: Thomas Poignant <thomas.poignant@gofeatureflag.org>

* adding timeout

Signed-off-by: Thomas Poignant <thomas.poignant@gofeatureflag.org>

---------

Signed-off-by: Thomas Poignant <thomas.poignant@gofeatureflag.org>

* feat: Group API in core and extensions (#23)

Signed-off-by: Thomas Poignant <thomas.poignant@gofeatureflag.org>

* Update guideline/static-context-provider.md

Co-authored-by: Michael Beemer <beeme1mr@users.noreply.github.com>
Signed-off-by: Thomas Poignant <thomas.poignant@gmail.com>
Signed-off-by: Thomas Poignant <thomas.poignant@gofeatureflag.org>

* Update openapi.yaml

Signed-off-by: Thomas Poignant <thomas.poignant@gofeatureflag.org>

---------

Signed-off-by: Thomas Poignant <thomas.poignant@gofeatureflag.org>
Signed-off-by: Thomas Poignant <thomas.poignant@gmail.com>
Signed-off-by: Kavindu Dodanduwa <kavindudodanduwa@gmail.com>
Signed-off-by: Michael Beemer <beeme1mr@users.noreply.github.com>
Signed-off-by: Roman Dmytrenko <rdmytrenko@gmail.com>
Signed-off-by: Kavindu Dodanduwa <Kavindu-Dodan@users.noreply.github.com>
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
Signed-off-by: Honza Dvorsky <honza@apple.com>
Co-authored-by: Mark Phelps <209477+markphelps@users.noreply.github.com>
Co-authored-by: Michael Beemer <beeme1mr@users.noreply.github.com>
Co-authored-by: Kavindu Dodanduwa <Kavindu-Dodan@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Lukas Reining <lukas.reining@codecentric.de>
Co-authored-by: Michel TURPIN <michel.turpin1@gmail.com>
Co-authored-by: Roman Dmytrenko <rdmytrenko@gmail.com>
Co-authored-by: Todd Baert <todd.baert@dynatrace.com>
Co-authored-by: Honza Dvorsky <honza@apple.com>
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.

6 participants