feat: Lazy identity-flag evaluation in local-eval mode#200
Open
feat: Lazy identity-flag evaluation in local-eval mode#200
Conversation
``get_identity_flags`` now returns a ``Flags`` that holds the
evaluation context plus a precomputed segment-overrides reverse index,
and resolves each feature on first access via the engine primitives
(``is_context_in_segment`` + ``get_flag_result_from_context``) rather
than running a full bulk evaluation up-front.
In environments shaped like the Slack-report customer (420 features,
30 CSV-IN segments, hot loop reading one boolean flag) this takes
``get_identity_flags().is_feature_enabled(name)`` from ~430 µs to
~1.85 µs per call; 200-segment envs go from ~1200 µs to ~2 µs. The
``.all_flags()`` materialisation path is never slower than the
eager baseline in the bench matrix.
Back-compat:
* ``Flags`` public API unchanged (``is_feature_enabled``,
``get_feature_value``, ``get_flag``, ``all_flags``).
* ``FlagResult`` construction reuses the same engine helper as the
bulk path — identical output shape.
* New ``lazy_identity_evaluation`` constructor kwarg, default
``True``, lets operators flip back to the eager path if they hit
an unexpected regression.
Engine contract is untouched: the SDK consumes only already-public
``flag_engine.segments.evaluator`` symbols.
beep boop
Picks up the IN segment-condition evaluation speedup (Flagsmith/flagsmith-engine#295), which cuts per-IN-condition latency on segment walks by roughly 30%. Complementary to the lazy identity evaluation added in this PR — most customer envs will benefit from both. beep boop
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.
Contributes to #198.
Flagsmith.get_identity_flagsfor local evaluation. The returnedFlagsnow holds the evaluation context plus a precomputed segment-overrides reverse index and resolves each feature on first access via the engine's already-publicis_context_in_segment/get_flag_result_from_contextprimitives, instead of running a full bulk evaluation up-front._evaluation_contextsetter, so it stays in sync with environment refreshes with zero hot-path cost.lazy_identity_evaluation: bool = Trueconstructor kwarg acts as a rollback switch; set toFalseto opt back into the legacy eager path with the exact previous behaviour.Why
From customer report: 420-feature local-eval env, hot loop reading one boolean flag, 400+ µs per call. Every call was re-evaluating the whole environment document even though the caller only reads a single flag.
Bench (off main,
poetry run python -m benchmarks.bench)all_flags().all_flags()is faster too because each lazy per-flag resolution walks only the segments that target that flag (via the reverse index), rather than every segment across every feature.Back-compat
Flagspublic surface unchanged (is_feature_enabled,get_feature_value,get_flag,all_flags, dataclass shape).FlagResultconstruction reuses the same engine helper as the bulk path — identical output shape, no reason-string drift, no hidden branching.get_identity_flagsto the first flag read; wrap-in-try keeps semantics for any caller that already defends against it.lazy_identity_evaluation=Falserestores the old timing byte-for-byte if needed.Tests
tests/test_models.py(match / no-match / per-flag caching /all_flagsmaterialisation / default-handler fallthrough / reverse-index correctness).tests/test_flagsmith.py: lazy-by-default (no bulk engine call) and rollback kwarg (eager bulk call).