Skip to content

Commit 528504f

Browse files
skyerustoddbaert
andauthored
feat: flagd caching (#168)
Signed-off-by: Skye Gill <[email protected]> Co-authored-by: Todd Baert <[email protected]>
1 parent 0ec88d4 commit 528504f

File tree

8 files changed

+738
-61
lines changed

8 files changed

+738
-61
lines changed

Diff for: providers/flagd/README.md

+18-7
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,16 @@ OpenFeatureAPI.getInstance().setProvider(provider);
2626

2727
Options can be defined in the constructor or as environment variables, with constructor options having the highest precedence.
2828

29-
| Option name | Environment variable name | Type | Default |
30-
| ----------- | ------------------------- | ------- | --------- |
31-
| host | FLAGD_HOST | string | localhost |
32-
| port | FLAGD_PORT | number | 8013 |
33-
| tls | FLAGD_TLS | boolean | false |
34-
| socketPath | FLAGD_SOCKET_PATH | string | - |
35-
| certPath | FLAGD_SERVER_CERT_PATH | string | - |
29+
| Option name | Environment variable name | Type | Default | Values |
30+
| --------------------- | ------------------------------- | ------- | --------- | ------------- |
31+
| host | FLAGD_HOST | string | localhost | |
32+
| port | FLAGD_PORT | number | 8013 | |
33+
| tls | FLAGD_TLS | boolean | false | |
34+
| socketPath | FLAGD_SOCKET_PATH | string | - | |
35+
| certPath | FLAGD_SERVER_CERT_PATH | string | - | |
36+
| cache | FLAGD_CACHE | string | lru | lru,disabled |
37+
| maxCacheSize | FLAGD_MAX_CACHE_SIZE | int | 1000 | |
38+
| maxEventStreamRetries | FLAGD_MAX_EVENT_STREAM_RETRIES | int | 5 | |
3639

3740
### Unix socket support
3841

@@ -53,3 +56,11 @@ The default deadline is 500ms, though evaluations typically take on the order of
5356
Though not required in deployments where flagd runs on the same host as the workload, TLS is available.
5457

5558
:warning: Note that there's a [vulnerability](https://security.snyk.io/vuln/SNYK-JAVA-IONETTY-1042268) in [netty](https://github.com/netty/netty), a transitive dependency of the underlying gRPC libraries used in the flagd-provider that fails to correctly validate certificates. This will be addressed in netty v5.
59+
60+
## Caching
61+
62+
The provider attempts to establish a connection to flagd's event stream (up to 5 times by default). If the connection is successful and caching is enabled each flag returned with reason `STATIC` is cached until an event is received concerning the cached flag (at which point it is removed from cache).
63+
64+
On invocation of a flag evaluation (if caching is available) an attempt is made to retrieve the entry from cache, if found the flag is returned with reason `CACHED`.
65+
66+
By default, the provider is configured to use [least recently used (lru)](https://commons.apache.org/proper/commons-collections/apidocs/org/apache/commons/collections4/map/LRUMap.html) caching with up to 1000 entries.

Diff for: providers/flagd/pom.xml

+6
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,12 @@
6161
<version>6.0.53</version>
6262
<scope>provided</scope>
6363
</dependency>
64+
65+
<dependency>
66+
<groupId>org.apache.commons</groupId>
67+
<artifactId>commons-collections4</artifactId>
68+
<version>4.4</version>
69+
</dependency>
6470
</dependencies>
6571

6672
<build>

Diff for: providers/flagd/schemas

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package dev.openfeature.contrib.providers.flagd;
2+
3+
/**
4+
* Defines behaviour required of event stream callbacks.
5+
*/
6+
interface EventStreamCallback {
7+
void setEventStreamAlive(Boolean alive);
8+
9+
void restartEventStream() throws Exception;
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package dev.openfeature.contrib.providers.flagd;
2+
3+
import java.util.Map;
4+
import io.grpc.stub.StreamObserver;
5+
import lombok.extern.slf4j.Slf4j;
6+
import dev.openfeature.flagd.grpc.Schema.EventStreamResponse;
7+
import com.google.protobuf.Value;
8+
9+
/**
10+
* EventStreamObserver handles events emitted by flagd.
11+
*/
12+
@Slf4j
13+
public class EventStreamObserver implements StreamObserver<EventStreamResponse> {
14+
private EventStreamCallback callback;
15+
private FlagdCache cache;
16+
17+
private static final String configurationChange = "configuration_change";
18+
private static final String providerReady = "provider_ready";
19+
private static final String flagsKey = "flags";
20+
21+
EventStreamObserver(FlagdCache cache, EventStreamCallback callback) {
22+
this.cache = cache;
23+
this.callback = callback;
24+
}
25+
26+
@Override
27+
public void onNext(EventStreamResponse value) {
28+
switch (value.getType()) {
29+
case configurationChange:
30+
this.handleConfigurationChangeEvent(value);
31+
break;
32+
case providerReady:
33+
this.handleProviderReadyEvent();
34+
break;
35+
default:
36+
log.debug("unhandled event type {}", value.getType());
37+
return;
38+
}
39+
}
40+
41+
@Override
42+
public void onError(Throwable t) {
43+
log.error("event stream", t);
44+
this.cache.clear();
45+
this.callback.setEventStreamAlive(false);
46+
try {
47+
this.callback.restartEventStream();
48+
} catch (Exception e) {
49+
log.error("restart event stream", e);
50+
}
51+
}
52+
53+
@Override
54+
public void onCompleted() {
55+
this.cache.clear();
56+
this.callback.setEventStreamAlive(false);
57+
}
58+
59+
private void handleConfigurationChangeEvent(EventStreamResponse value) {
60+
if (!this.cache.getEnabled()) {
61+
return;
62+
}
63+
64+
Map<String, Value> data = value.getData().getFieldsMap();
65+
Value flagsValue = data.get(flagsKey);
66+
if (flagsValue == null) {
67+
this.cache.clear();
68+
return;
69+
}
70+
71+
Map<String, Value> flags = flagsValue.getStructValue().getFieldsMap();
72+
73+
for (String flagKey : flags.keySet()) {
74+
this.cache.remove(flagKey);
75+
}
76+
}
77+
78+
private void handleProviderReadyEvent() {
79+
this.cache.clear();
80+
this.callback.setEventStreamAlive(true);
81+
}
82+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package dev.openfeature.contrib.providers.flagd;
2+
3+
import dev.openfeature.sdk.ProviderEvaluation;
4+
import dev.openfeature.sdk.Value;
5+
6+
import java.util.Map;
7+
import org.apache.commons.collections4.map.LRUMap;
8+
import java.util.Collections;
9+
10+
/**
11+
* Exposes caching mechanism for flag evaluations.
12+
*/
13+
public class FlagdCache {
14+
private Map<String,ProviderEvaluation<Value>> store;
15+
private Boolean enabled;
16+
17+
static final String LRU_CACHE = "lru";
18+
static final String DISABLED = "disabled";
19+
20+
FlagdCache(String cache, int maxCacheSize) {
21+
switch (cache) {
22+
case DISABLED:
23+
return;
24+
case LRU_CACHE:
25+
default:
26+
this.store = Collections.synchronizedMap(new LRUMap<String, ProviderEvaluation<Value>>(maxCacheSize));
27+
}
28+
29+
this.enabled = true;
30+
}
31+
32+
public Boolean getEnabled() {
33+
return this.enabled;
34+
}
35+
36+
public void put(String key, ProviderEvaluation<Value> value) {
37+
this.store.put(key, value);
38+
}
39+
40+
public ProviderEvaluation<Value> get(String key) {
41+
return this.store.get(key);
42+
}
43+
44+
public void remove(String key) {
45+
this.store.remove(key);
46+
}
47+
48+
public void clear() {
49+
this.store.clear();
50+
}
51+
}

0 commit comments

Comments
 (0)