feat: Add support for /decide?v=4 along with start capturing $feature_flag_called events#22
feat: Add support for /decide?v=4 along with start capturing $feature_flag_called events#22
/decide?v=4 along with start capturing $feature_flag_called events#22Conversation
This ensures backwards comatibility
Less confusing this way.
If the `$feature_flag_called` event is called twice in succession with the same distinct_id and same key, then we only call it once. We do this by implementing an LRU cache of up to 50,000 entries (a number picked from `posthog-python`).
There was a problem hiding this comment.
Pull Request Overview
This PR adds support for utilizing the new /decide?v=4 endpoint and enhances the feature flag capture mechanism by renaming the value property to payload and including additional metadata on the $feature_flag_called event.
- Updated documentation examples to use "payload" instead of "value" for the feature flag's return object
- Added instructions for code formatting via bin/fmt in the main README
Reviewed Changes
Copilot reviewed 4 out of 15 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| examples/feature_flag_demo/README.md | Updated feature flag demo to show "Payload: true" instead of "Value: true" |
| README.md | Revised code examples to reflect the change from "value" to "payload" and added a code formatting section |
Files not reviewed (11)
- examples/feature_flag_demo/lib/feature_flag_demo.ex: Language not supported
- lib/posthog.ex: Language not supported
- lib/posthog/application.ex: Language not supported
- lib/posthog/client.ex: Language not supported
- lib/posthog/feature_flag.ex: Language not supported
- mix.exs: Language not supported
- test/posthog/client_test.exs: Language not supported
- test/posthog_test.exs: Language not supported
- test/support/fixtures/decide-v3.json: Language not supported
- test/support/fixtures/decide.json: Language not supported
- test/support/hackney_stub.ex: Language not supported
Comments suppressed due to low confidence (2)
README.md:112
- [nitpick] Ensure consistency in property naming across the documentation. The example now uses 'payload' instead of 'value', so please confirm that related documentation (such as the event schema table in the PR description) is updated accordingly.
# Returns: %Posthog.FeatureFlag{name: "new-dashboard", payload: true, enabled: true}
README.md:118
- [nitpick] Ensure naming consistency for the feature flag return value. The change from 'value' to 'payload' should be reflected across all documentation and examples to avoid confusion.
# payload: %{"price" => 99, "period" => "monthly"},
|
Hmm, the CI server is reporting: Run mix format --check-formatted
** (Mix) mix format failed due to --check-formatted.
The following files are not formatted:
* test/posthog_test.exsBut when I run Going to try using an older version of elixir on my local machine to see if it fixes the formatting issue. |
|
Ok, formatting issues are fixed, but some of the tests for older versions of elixir are broken. How far back do we need to go? |
1.12 is reasonable, I think we should keep it. Tests are failing because you're using |
|
About formatting, we should make sure the CI is only checking formatting on the most recent version |
lib/posthog/application.ex
Outdated
| children = [ | ||
| # Start Cachex for feature flag event deduplication. | ||
| # The 50,000 entries limit is the same used for posthog-python, but otherwise arbitrary. | ||
| {Cachex, name: :posthog_feature_flag_cache, limit: 50_000, policy: Cachex.Policy.LRW} |
There was a problem hiding this comment.
Does this make sense? Do we not wanna send $feature_flag_called in case we have this in the cache? This lasts across requests - it's a separate process.
Wont this mean that users with less than 50000 clients wont ever have more than 50000 FFs charged to them? (Users can make these supervisors survive servers restarts, Elixir is crazy)
There was a problem hiding this comment.
Well, it's the combination of distinct_id and feature_flag. So yeah, if they only have 1 flag and less than 50,000 distinct users, they'd never be charged for more than 50,000.
I took this approach from posthog-python. Many of the other clients do something similar. It seems to me that we should put a time limit on each cache entry. Something like one minute. @dmarticus, do you have context on why we do this in posthog-python?
There was a problem hiding this comment.
Ah, the hash includes the feature_flag too, so it's a slightly smaller problem.
The problem is that servers probably restart pretty often in Python; that isn't necessarily true in Elixir: you can keep the Cachex process running indefinitely, so... it might fill the cache pretty quickly.
Will leave that decision up to y'all, just letting you know the quirkyness of working with a distributed language :)
There was a problem hiding this comment.
One other thing to consider, we don't charge based on the number of $feature_flag_called events, we charge based on the number of /decide calls made. So this cache doesn't affect the cost. Just reporting.
There was a problem hiding this comment.
Ah, I didn't know that. In that case, I'm fine with keeping it this way. I'm working on improving the setup, I don't think the LRU we added actually works how we think, expect changes to this PR through the day.
There was a problem hiding this comment.
The one thing I worry about with such a big cache is people being confused why they don't see these events. My gut tells me the reason for this is if they're calling a feature flag in a tight loop, we don't want 10 of the same events all of a sudden.
So I'd suggest we add a default_ttl to the Cachex declaration. Something like 5 minutes would be fine. The 50,000 limit then just ensures we don't ever take up too much memory.
Any suggestions? |
Hmm, I have a GenServer approach that seems to work. |
|
@haacked I'm taking a stab at this. I dont think it was the |
Elixir has been improving their typesystem, and if we run these doctests with more recent Elixir (1.18+) then it fails at the type level - which is pretty cool, actually!
Our CI matrix is much more comprehensible of Elixir's actual support matrix, at the cost of being more complex. I think it's a fair tradeoff.
erlef/setup-beam@v1 doesn't support OTP 23
|
@haacked I've changed this to point to a new |
I'm not in any hurry. I appreciate the help getting this over the line! |
Organized it into a couple functions and also added documentation around it
rafaeelaudibert
left a comment
There was a problem hiding this comment.
@haacked This should be good now! I've pointed it to a v1 branch where I'm working on more stuff. Take a look at my recent changes and let me know! Feel free to merge whenever you're ready
|
@rafaeelaudibert what do you think about giving cache entries a ttl? I think Cachex supports that? |
Up to you, really. I particularly think it's to your best interest to keep implementation across SDKs the most similar possible, but if you believe a TTL would be benefitial here, yeah, we can do it |
Good point. I'll leave as-is. If folks complain, we can revisit. I also pushed some changes to get the sample app working again. Look good to you? |
|
@haacked Did you need that new |
|
Here's the error I get when I run the demo app without the new |
💡 Motivation and Context
Prior to this PR, this library didn't capture
$feature_flag_calledevents. Now it does it with the additional metadata that/decide?v=4provides (see PostHog/posthog#29751). Similar to how it's done in PostHog/posthog-js-lite#427.Updates to the
$feature_flag_calledeventWhen capturing the
$feature_flag_calledevent, additional information are now captured:The end result is
$feature_flag_calledevents will have additional properties:$feature_flag_version$feature_flag_reason$feature_flag_id$feature_flag_request_id/deciderequest.Also, we use an LRU to ensure that we don't raise the
$feature_flag_calledevent twice for the samedistinct_idand flagkeycombination, the same wayposthog-pythonand other libraries do it.Backwards compatibility:
The changes are all backwards compatible with
/decide?v=3.💚 How did you test it?
Unit tests and manual tests.