flagd
flagd copied to clipboard
chore(ADR): Create fractional-non-string-rand-units.md
This PR
- Proposes
Support Non-String Inputs for Fractional BucketingADR
Related Issues
https://github.com/open-feature/flagd/issues/1737
Deploy Preview for polite-licorice-3db33c ready!
| Name | Link |
|---|---|
| Latest commit | a50f893a91628d01b26a33e458b30bd52aef0831 |
| Latest deploy log | https://app.netlify.com/projects/polite-licorice-3db33c/deploys/68b7287d959aea00081dbece |
| Deploy Preview | https://deploy-preview-1783--polite-licorice-3db33c.netlify.app |
| Preview on mobile | Toggle QR Code...Use your smartphone camera to open QR code link. |
To edit notification comments on pull requests, go to your Netlify project configuration.
I've been thinking about this more and I wonder WDYT about this approach:
We take this pre-1.0 opportunity to introduce a breaking change for how hashing / bucketing works:
- If fractional arguments are all arrays, then we use
targetingKeyfor hashing, similar to today - Otherwise, the first argument needs to be an object (
{})
If the first argument is an object, it's evaluated and the evaluated value is passed to CBOR for marshalling into a consistent byte array. That byte array is passed to MurMur3 for hashing.
If targetingKey is used, a 2 element array is created with the first element being the flag key, and the second element being the targetingKey value. That array is passed to CBOR for marshalling and the resulting byte array is passed to MurMur3 for hashing.
CBOR libraries in each language ensure that the same values get the same byte encoding and murmur3 libraries will ensure that same byte arrays will get the same hash. This works across any type, even for strings.
That way we have langauge-agnostic, stable, and consistent bucketing.
CBOR is specified in an Internet Standard RFC by IETF, which should mean this stays stable for foreseeable future.
We take this pre-1.0 opportunity to introduce a breaking change for how hashing / bucketing works:
- If fractional arguments are all arrays, then we use
targetingKeyfor hashing, similar to today- Otherwise, the first argument needs to be an object (
{})
@cupofcat would you mind showing an example configuration of what you had in mind? When you say object, do you mean referencing a context attribute { "var": "useId" } or something else?
We take this pre-1.0 opportunity to introduce a breaking change for how hashing / bucketing works:
- If fractional arguments are all arrays, then we use
targetingKeyfor hashing, similar to today- Otherwise, the first argument needs to be an object (
{})@cupofcat would you mind showing an example configuration of what you had in mind? When you say object, do you mean referencing a context attribute
{ "var": "useId" }or something else?
I meant that essentially the first item in fractional is {...} and not a list. It can be { var: ...} or something else. I'm open to other suggestions, we just need some way to detect if we should use targetingKey or evaluate the first argument for hashing input.
I would want to keep it pretty generic though. In theory, a list of elements could also be a valid input to hashing.
Edit: The above did not make sense, sorry.
@beeme1mr I've been thinking about this more and I think we have the following options:
- Similarly to today we have an optional 1st argument to fractional to provide value to hash for bucketing.
- We make the first argument mandatory; if someone wants to use
targetingKeythey need to providenullas first argument - We introduce a new custom operator to be used inside
fractionalA. something likecbor-8949-bytesthat is guaranteed to return a consistent byte array in every language B. something likeobjectthat preserves the keys and evaluates the values C. we can brainstorm other custom operator with other semantics too
For (1), since the first argument is optional, we need a way to distinguish it from the subsequent ones. Today, this is done based on type (if it casts to a string, it's treated specially). That means the first argument, if the user wants to use it for bucketing, cannot evaluate to an array. If it is an array we cannot distinguish it from the rest of fractional values. We could go further, and look into types inside the array and say it cannot start with [string, number] but overall I think this is a really hacky semantic. We could say, that if it evaluates to a map/dictionary/object then we encode it to bytes and hash that. However, I cannot seem to figure out how to return an object in JSONLogic using existing operators...
(2) is much clearer, but the semantic of the current schema changes (the schema itself can stay the same). We make the first argument mandatory as the bucketing input. If it's null we use the targetingKey. This is a pretty big breaking change, as all the flag configs that rely on targetingKey would break. But, perhaps, this is OK when moving to 1.0.
(3A) Similarly to today, if the first value in fractional array is bytes we hash that. If not, we use the current behavior as is.
"fractional": [
{
"cbor-8949-bytes": [{"var" : "$flagd.flagKey"}, {"var" : "some-var"}]
},
["a", 50], ...
]
(3B) Similarly to today, if the first value in fractional array is a map/dict we hash that. If not, we use the current behavior as is.
"fractional": [
{
"object": {
"comment" : "Any keys are valid, we will just translate all of this to bytes (JSONLogic would still evaluate the values)",
"key1" : {"var" : "$flagd.flagKey"},
"key2" : {"var" : "some-var"},
"extra-salt" : "himalayan"
}
},
["a", 50], ...
]
If none of the above works, perhaps there is also:
- We introduce a new custom operator replacing
fractional. We can call itfractional1orfractionalWithKeyorfractionalBytesor something. We make it officially recommended for flagd 1.0 and we deprecatefractional. We could keep evaluatingfractionalusing current logic with warnings. We could also remove it completely from 1.0, given that this will be a pretty big breaking change anyway for the users.
WDYT?
P.S. What I find complicates this exercise is that today we send the entire contents of targeting to JSONLogic for evaluation. So we cannot really do any parsing on the structure of the object. Our fractional evaluation implementation already receives evaluated values so we don't know what the arguments looked like.
So I guess there is also:
- Parse either
targetingorfractionallooking for specific fields to control the behavior of what gets passed to JSONLogic and how.
Based on the discussions on Slack I updated this ADR to focus on hashing improvements in the existing fractional only.
I updated the ADR to explicitly require CBOR encodings + some other minor improvements based on feedback.
I am in favor of this proposal, but I can't help but consider this a bit of a blocker.
@beeme1mr WDYT about this one? If we cant append the flag key, should we remove this in the string case? I'd prefer not to have string as a "special case".
Quality Gate passed
Issues
0 New issues
0 Accepted issues
Measures
0 Security Hotspots
0.0% Coverage on New Code
0.0% Duplication on New Code
I am in favor of this proposal, but I can't help but consider this a bit of a blocker.
@beeme1mr WDYT about this one? If we cant append the flag key, should we remove this in the string case? I'd prefer not to have string as a "special case".
I would not necessarily consider this a special case for strings. It's a result of JSONLogic rather than flagd approach (JSON Logic has "cat" operator for strings but does not have anything like that for non-string types). We can fix that by implementing an additional custom operator.
That way we keep the new approach backwards compatible with old configs (and, tbh, there is no way around this - "cat" is a feature of JSONLogic, not flagd, so we cannot really block it unless we do some hacks).