-
-
Notifications
You must be signed in to change notification settings - Fork 459
Add scope based feature flags #4812
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Instructions and example for changelogPlease add an entry to Example: ## Unreleased
### Features
- Add scope based feature flags ([#4812](https://github.com/getsentry/sentry-java/pull/4812)) If none of the above apply, you can opt out of this check by adding |
Performance metrics 🚀
|
Revision | Plain | With Sentry | Diff |
---|---|---|---|
b3d8889 | 420.46 ms | 453.71 ms | 33.26 ms |
ee747ae | 386.94 ms | 431.43 ms | 44.49 ms |
ee747ae | 400.46 ms | 423.61 ms | 23.15 ms |
9fbb112 | 361.43 ms | 427.57 ms | 66.14 ms |
cf708bd | 408.35 ms | 458.98 ms | 50.63 ms |
ee747ae | 396.82 ms | 441.67 ms | 44.86 ms |
cf708bd | 434.73 ms | 502.96 ms | 68.22 ms |
bdbe1f4 | 380.66 ms | 464.44 ms | 83.78 ms |
806307f | 357.85 ms | 424.64 ms | 66.79 ms |
f634d01 | 375.06 ms | 420.04 ms | 44.98 ms |
App size
Revision | Plain | With Sentry | Diff |
---|---|---|---|
b3d8889 | 1.58 MiB | 2.10 MiB | 535.07 KiB |
ee747ae | 1.58 MiB | 2.10 MiB | 530.95 KiB |
ee747ae | 1.58 MiB | 2.10 MiB | 530.95 KiB |
9fbb112 | 1.58 MiB | 2.11 MiB | 539.18 KiB |
cf708bd | 1.58 MiB | 2.11 MiB | 539.71 KiB |
ee747ae | 1.58 MiB | 2.10 MiB | 530.95 KiB |
cf708bd | 1.58 MiB | 2.11 MiB | 539.71 KiB |
bdbe1f4 | 1.58 MiB | 2.11 MiB | 538.88 KiB |
806307f | 1.58 MiB | 2.10 MiB | 533.42 KiB |
f634d01 | 1.58 MiB | 2.10 MiB | 533.40 KiB |
tmpList.remove(0); | ||
} | ||
|
||
flags = new CopyOnWriteArrayList<>(tmpList); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does it need to be CopyOnWrite if we never actually call flags.add()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I opted for CopyOnWriteArrayList
due to it being a good choice for scope cloning without affecting parent scopes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Gotta think about whether using something else would work too, probably yes but what's the benefit?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be possible to replace with ArrayList
but then we must never modify the collection. I was still hoping for some enlightenment on how to speed up the add
method. I've written this down and depending on how the add method turns out, we can replace it.
We could in theory wrap it with Collections.unmodifyableList
but I'd rather send corrupted data as opposed to crashing the customers application.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
but then we must never modify the collection.
why is that? sorry for the questions I guess i'm missing some context 😅
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Discussed in more detail internally.
We currently optimized feature flags storage for scope cloning performance (where CopyOnWriteArrayList
simply re-uses the internal array, when cloning the list, which is O(1)).
Merging was our second priority.
Add is currently rather inefficient.
We wanna release as is and if customers request better performance, we can revisit and create an Android specific implementation, that prioritizes differently and/or optimizes for fewer allocations.
private @NotNull String flag; | ||
|
||
/** Evaluation result of the feature flag. */ | ||
private boolean result; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Flag evaluation is allowed to be an arbitrary JSON according to Relay https://github.com/getsentry/relay/blob/21c2e18ebe6f834a4ce4e149c6a43e4bec1e90f8/relay-event-schema/src/protocol/contexts/flags.rs#L30
However in docs and frontend this is typed as bool.
I think it will be easy to extend this with an overload that takes Object or we just change this to take it into account from the get go.
Let's also double check with @cmanallen on how to proceed here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Relay is set up to accommodate remote-configs/feature-flags of any JSON type. The UI currently expects boolean though. I'm not sure how resilient it is to non-boolean types. The goal is to expand our UI to accommodate more types in the future but AFAIK that's not been realized yet. Something I can find out later today.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@cmanallen can we release this as is with Boolean
param exposed to customers?
final int size = flags.size(); | ||
final @NotNull ArrayList<FeatureFlagEntry> tmpList = new ArrayList<>(size + 1); | ||
for (FeatureFlagEntry entry : flags) { | ||
if (!entry.flag.equals(flag)) { | ||
tmpList.add(entry); | ||
} | ||
} | ||
tmpList.add(new FeatureFlagEntry(flag, result, System.nanoTime())); | ||
|
||
if (tmpList.size() > maxSize) { | ||
tmpList.remove(0); | ||
} | ||
|
||
flags = new CopyOnWriteArrayList<>(tmpList); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
final int size = flags.size(); | |
final @NotNull ArrayList<FeatureFlagEntry> tmpList = new ArrayList<>(size + 1); | |
for (FeatureFlagEntry entry : flags) { | |
if (!entry.flag.equals(flag)) { | |
tmpList.add(entry); | |
} | |
} | |
tmpList.add(new FeatureFlagEntry(flag, result, System.nanoTime())); | |
if (tmpList.size() > maxSize) { | |
tmpList.remove(0); | |
} | |
flags = new CopyOnWriteArrayList<>(tmpList); | |
final int size = flags.size(); | |
for (int i = 0; i < size; i++) { | |
if (flags.get(i).equals(flag)) { | |
flags.remove(i); | |
break; | |
} | |
} | |
flags.add(new FeatureFlagEntry(flag, result, System.nanoTime())); | |
if (flags.size() > maxSize) { | |
flags.remove(0); | |
} |
Maybe this is better?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice!
Yes, it's better, see #4812 (comment) and #4812 (comment)
This is called "Mutable CopyOnWriteArrayList" in the tables.
Table compares ops/s for
Here's the code I used to benchmark:
|
Reran benchmarks for a (hopefully) more realistic scenario NOTE: I just updated these after fixing a bug with the duplicate check
Added a new implementation which looks for the duplicate flag in reverse order:
|
Co-authored-by: Lorenzo Cian <17258265+lcian@users.noreply.github.com>
📜 Description
addFeatureFlag
can be used to track feature flag evaluations and have them show up on errors in Sentry💡 Motivation and Context
Scope based part of #4763
💚 How did you test it?
Manually + E2E tests + unit tests
📝 Checklist
sendDefaultPII
is enabled.🔮 Next steps
sentry.properties
)