feat(openfeature): add direct flagevaluation fallback - #12204
feat(openfeature): add direct flagevaluation fallback#12204leoromanovsky wants to merge 8 commits into
Conversation
This comment has been minimized.
This comment has been minimized.
🟢 Java Benchmark SLOs — All performance SLOs passed
PR vs. master results
Commit: Load and DaCapo benchmarks can be triggered manually in the GitLab pipeline. Results will appear in the Benchmarking Platform UI after completion. |
There was a problem hiding this comment.
Direct flag-evaluation delivery still negotiates gzip because IntakeApi leaves Accept-Encoding unset when compression is disabled, allowing OkHttp to add gzip automatically. This defeats the explicit no-compression contract on every new direct or fallback route, although the local EVP path correctly sends identity.
🤖 Datadog Autotest · Commit 5916539 · What is Autotest? · @DataDog review to ask questions · Any feedback? Reach out in #autotest
| return null; | ||
| } | ||
| try { | ||
| return backendApiFactory.createDirectIntakeApi(Intake.EVENT_PLATFORM, responseCompression); |
There was a problem hiding this comment.
Direct intake ignores disabled response compression
Every agentless flag-evaluation request using the new direct or fallback route still negotiates gzip, defeating the route’s stated no-compression compatibility contract.
Assertion details
- Input: Agentless feature flagging with an API key when the local EVP proxy is unavailable or definitively rejects flag-evaluation delivery.
- Expected:
Direct flag-evaluation requests should send Accept-Encoding: identity so response compression is genuinely disabled on both local and direct routes. - Actual:
The new direct path passes responseCompression=false to createDirectIntakeApi, but IntakeApi only adds Accept-Encoding when the value is true. OkHttp therefore automatically adds Accept-Encoding: gzip. The sibling EvpProxyApi explicitly sends identity on its false path to prevent exactly this behavior. FeatureFlagBackendApiFactoryTest only verifies the boolean argument, while the direct writer test does not inspect Accept-Encoding. A complete fix must update IntakeApi to send Accept-Encoding: identity when compression is disabled and cover that shared-client behavior; changing this factory line alone is insufficient.
Was this helpful? React 👍 or 👎
🤖 Datadog Autotest · What is Autotest? · @DataDog review to ask questions · Any feedback? Reach out in #autotest
| capacity, | ||
| flushInterval, | ||
| timeUnit, | ||
| new FeatureFlagBackendApiFactory(config, sco, "exposure", true), |
There was a problem hiding this comment.
nitpick: reading the code, I've no idea what true means here. Not sure what java conventions are but an enum or a builder would help. (although builder for a factory is a bit 🤪.) A builder-like setter also works
| final Config config, | ||
| final SharedCommunicationObjects sharedCommunicationObjects, | ||
| final String eventType, | ||
| final boolean responseCompression) { |
There was a problem hiding this comment.
Q: What is "response compression?" Why is it disabled for flag evaluations?
| private static final Logger LOGGER = | ||
| LoggerFactory.getLogger(AgentlessFeatureFlagBackendApi.class); | ||
|
|
||
| private final BackendApi localApi; |
There was a problem hiding this comment.
aside: probably not in scope of this PR but it took me a bit to figure out what "local API" means. I think that "proxy" is the more important part of the name, and "direct vs. proxy" makes more sense as a dichotomy
I guess "local" part might also be false when agent is deployed to a different node in a cluster
…ct-flagevaluation-egress # Conflicts: # products/feature-flagging/feature-flagging-lib/src/main/java/com/datadog/featureflag/AgentlessFeatureFlagBackendApi.java # products/feature-flagging/feature-flagging-lib/src/main/java/com/datadog/featureflag/ExposureBackendApiFactory.java # products/feature-flagging/feature-flagging-lib/src/test/java/com/datadog/featureflag/AgentlessFeatureFlagBackendApiTest.java # products/feature-flagging/feature-flagging-lib/src/test/java/com/datadog/featureflag/FeatureFlagBackendApiFactoryTest.java
|
Hi! 👋 Thanks for your pull request! 🎉 To help us review it, please make sure to:
If you need help, please check our contributing guidelines. |
…ct-flagevaluation-egress # Conflicts: # products/feature-flagging/feature-flagging-lib/src/test/java/com/datadog/featureflag/AgentlessFeatureFlagBackendApiTest.java # products/feature-flagging/feature-flagging-lib/src/test/java/com/datadog/featureflag/FeatureFlagBackendApiFactoryTest.java
…ct-flagevaluation-egress
…ct-flagevaluation-egress
…ct-flagevaluation-egress
sarahchen6
left a comment
There was a problem hiding this comment.
A few small clean-up comments, but otherwise looks good!
There was a problem hiding this comment.
side comment: Github UI didn't recognize ExposureBackendApiFactory->FeatureFlagBackendApiFactory as a ~rename, but did for AgentlessExposureBackendApi->AgentlessFeatureFlagBackendApi......booo
| * EVP flagevaluation writer for Java. | ||
| * | ||
| * <p>Uses the same EVP publisher path as ExposureWriterImpl, with two-tier aggregation replacing | ||
| * the single-exposure buffer. Routes to the Agent-advertised EVP proxy endpoint for |
There was a problem hiding this comment.
now direct EVP instead of just Agent (local) EVP is possible?
| API_KEY, | ||
| "123", | ||
| HttpRetryPolicy.Factory.NEVER_RETRY, | ||
| new OkHttpClient.Builder().build(), |
There was a problem hiding this comment.
nit: should clean up this client like done in IntakeApiTest
| private final AtomicBoolean shutdownRequested = new AtomicBoolean(false); | ||
| private final CountDownLatch finalFlushDone = new CountDownLatch(1); | ||
|
|
||
| FlagEvaluationSerializingHandler( |
There was a problem hiding this comment.
Can we reduce the amount of constructors for FlagEvaluationWriterImpl and FlagEvaluationSerializingHandler?
I checked this quickly by Codex, and it gave the following suggestion:
The writer ultimately converts everything into Supplier<BackendApi> at
products/feature-flagging/feature-flagging-lib/src/main/java/com/datadog/
featureflag/FlagEvaluationWriterImpl.java:149. The handler does the same at
products/feature-flagging/feature-flagging-lib/src/main/java/com/datadog/
featureflag/FlagEvaluationWriterImpl.java:409. That should be the shared
boundary.
A cleaner shape would be:
public FlagEvaluationWriterImpl(
SharedCommunicationObjects sco, Config config) {
this(
DEFAULT_CAPACITY,
FLUSH_INTERVAL_SECONDS,
SECONDS,
new FeatureFlagBackendApiFactory(
config, sco, FeatureFlagEventType.FLAG_EVALUATION)
::create,
config);
}
FlagEvaluationWriterImpl(
int capacity,
long flushInterval,
TimeUnit timeUnit,
Supplier<BackendApi> backendApiSupplier,
Config config) {
// Actual initialization
}
Then the handler needs only its full constructor:
FlagEvaluationSerializingHandler(
Supplier<BackendApi> backendApiSupplier,
MessagePassingBlockingQueue<FlagEvalEvent> queue,
long flushInterval,
TimeUnit timeUnit,
Map<String, String> context,
AtomicLong droppedQueueOverflow,
ConcurrentHashMap<String, AtomicLong> contextTruncatedCounts,
Runnable errorCallback,
int payloadSizeLimitBytes) {
// Actual initialization
}
Production passes FeatureFlagBackendApiFactory::create; tests and benchmarks
pass a lambda or mocked supplier. That would allow removing:
- The sized SharedCommunicationObjects writer constructor at line 108.
- The BackendApiFactory writer constructor at line 123.
- The FeatureFlagBackendApiFactory writer constructor at line 140.
- Both handler constructors accepting BackendApiFactory at lines 345 and 366.
- Potentially the default-size Supplier handler constructor at line 388.
So the practical minimum is two writer constructors and one handler
constructor. The current nine combined overloads aren’t necessary.
Motivation
The base PR gives exposure events direct EVP capability. Agentless Feature Flags also need direct EVP capability for aggregate flagevaluation events when no compatible local receiver exists.
flowchart TD A[Agentless Feature Flags] --> E[Exposure writer from PR 12195] A --> F[Flagevaluation writer in this PR] E --> ER{Compatible local EVP route?} F --> FR{Compatible local EVP route?} ER -->|Yes| L[Local EVP proxy] FR -->|Yes| L ER -->|No, API key available| DE[Direct EVP<br/>/api/v2/exposures] FR -->|No, API key available| DF[Direct EVP<br/>/api/v2/flagevaluation] L --> P[Event Platform] DE --> P DF --> PChanges and Decisions
This stacked PR retains direct EVP exposure delivery from PR #12195 and adds direct EVP flagevaluation delivery.
/api/v2/exposuresand/api/v2/flagevaluationas separate direct routes.