perf(evaluation): add context key filtering and index-based WASM evaluation#70
Merged
Conversation
…uation
Reduce targeting flag evaluation latency for large contexts by avoiding
unnecessary data transfer across the WASM boundary.
Rust side:
- Walk compiled targeting trees to extract referenced context keys
(e.g. {"var": "email"} -> "email") during update_state()
- Return per-flag requiredContextKeys and flagIndices in UpdateStateResponse
- Add evaluate_by_index(u32, ctx_ptr, ctx_len) WASM export for O(1) flag
lookup by numeric index instead of string key HashMap lookup
- Add evaluate_flag_pre_enriched() that skips context enrichment when
$flagd is already present (host-side enrichment)
Java side:
- Cache requiredContextKeys and flagIndices from updateState() response
- EvaluationContextSerializer.serializeFiltered() serializes only the
context keys a targeting rule references, plus $flagd enrichment
- evaluateFlag(EvaluationContext) uses filtered serialization + index-based
eval when available, falls back to full serialization gracefully
- evaluateByIndex() calls the new WASM export with O(1) Vec lookup
JMH results (1000+ attribute LayeredEvaluationContext):
- Targeting flags: ~12.8 µs (down from ~167 µs) — 13x improvement
- vs old json-logic-java: 32-34x faster (409 µs -> 12.8 µs)
- Static/disabled flags: ~0.02 µs (pre-evaluated cache, unchanged)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The WASM tests use a thread-local singleton evaluator. Parallel test execution causes race conditions where one test's update_state overwrites another test's state, producing intermittent failures like test_wasm_evaluate_by_index getting "Static" instead of "TargetingMatch". Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Gherkin tests (cucumber) don't support --test-threads flag. Run lib and integration tests with --test-threads=1 for WASM singleton safety, and gherkin tests separately without the flag. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
update_state(), enabling host-side context filteringevaluate_by_index(u32, ctx_ptr, ctx_len)WASM export for O(1) flag lookup by numeric index$flagd.flagKey,$flagd.timestamp,targetingKey) to Java side, eliminating WASM-side cloneevaluateFlag(EvaluationContext)automatically applies filtered serialization and index-based evaluationHow it works
During
update_state(), the Rust side walks each flag's compiled targeting tree (CompiledNode) to find all{"var": "X"}references. This per-flag set of required context keys is returned in theUpdateStateResponsealongside a flag-key-to-index mapping.On the Java side,
evaluateFlag(EvaluationContext)uses these to:$flagdenrichment +targetingKey)evaluate_by_indexwith the numeric flag index instead of the string keyFor a 1000-attribute
LayeredEvaluationContextwhere the targeting rule checks 2 fields, context JSON shrinks from ~50KB to ~200 bytes.Benchmark results
JMH
ResolverComparisonBenchmark(1000+ attributeLayeredEvaluationContext, 1 fork, 5 measurement iterations):WASM evaluator: before → after optimization
main(µs)Optimized WASM evaluator vs old json-logic-java resolver
On
mainwithout context key filtering, the WASM evaluator was 5x slower than json-logic-java for large contexts (2935 µs vs 543 µs) because it serialized all 1000+ attributes to JSON, copied them to WASM memory, parsed them in Rust, and only then evaluated. With filtering, it serializes only the 2-3 fields the rule needs, making it 32x faster than the old resolver.Edge cases handled
{"var": ""}(entire context):extract_required_context_keysreturnsNone→ Java sends full contextevaluate_by_indexexport missing (older WASM): falls back to string-basedevaluate_reusablerequiredContextKeysabsent from response: falls back to full serializationsynchronizedblockTest plan
cargo test --lib --test integration_tests -- --test-threads=1— 134 passedcargo clippy -- -D warnings— cleancargo fmt -- --check— cleancd java && ./mvnw test— 37 passed (including 4 new FlagEvaluator tests + 3 new serializer tests)mainand feature branchCloses #60
🤖 Generated with Claude Code