-
Notifications
You must be signed in to change notification settings - Fork 375
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
fix: Speed up identity overrides #4840
base: main
Are you sure you want to change the base?
Conversation
The latest updates on your projects. Learn more about Vercel for Git ↗︎ 3 Skipped Deployments
|
Docker builds report
|
Uffizzi Ephemeral Environment
|
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.
Added a bunch of questions + not sure if the original issue is actually solved by this.
feature_ids: None | list[int] = None, | ||
limit_feature_identities_to_one_page: bool = False, | ||
) -> typing.List[dict[str, Any]]: | ||
try: | ||
return list( | ||
self.query_get_all_items( | ||
KeyConditionExpression=Key(ENVIRONMENTS_V2_PARTITION_KEY).eq( | ||
str(environment_id), | ||
if not limit_feature_identities_to_one_page: | ||
if feature_ids is not None: | ||
raise NotImplementedError( | ||
"Multiple feature ids is currently not supported " | ||
"when not limiting to a single page of features" |
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.
It seems like feature_id
, feature_ids
and limit_feature_identities_to_one_page
could be compressed to a single feature_ids
argument. This would significantly simplify the logic too.
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.
We need to keep the distinction between feature_id
and feature_ids
to support existing logic, but I was able to remove the limit_feature_identities_to_one_page
and rely on feature_ids
for the pre-existing logic.
executor.submit( | ||
self.get_page_of_feature_identities, | ||
environment_id, | ||
feature_id, | ||
) |
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.
This certainly looks like a nice solution — I haven't thought of running several queries in parallel when thinking of solving the issue.
Have you considered cases when one feature has >1MB of identity overrides data? The original issue mentioned introducing approximate counters for those (e.g. "1000+"
) but it doesn't seem to be implemented 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.
I've added a new field to mark identity overrides as being over the limit but this will need to be supported by the frontend with a new field.
except KeyError as e: | ||
raise ObjectDoesNotExist() from e | ||
|
||
def get_page_of_feature_identities( |
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.
Not a fan of introducing feature_identities
name while having established identity_overrides
. Also, not a fan of using prepositions in function names.
def get_page_of_feature_identities( | |
def get_identity_overrides_page( |
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.
Good idea. Updated.
def test_list_features_calls_get_overrides_data_with_feature_ids( | ||
dynamo_enabled_project: Project, | ||
dynamo_enabled_project_environment_one: Environment, | ||
admin_client_new: APIClient, | ||
mocker: MockerFixture, | ||
) -> None: | ||
# Given | ||
feature = Feature.objects.create( | ||
name="test_feature", project=dynamo_enabled_project | ||
) | ||
url = "%s?environment=%d" % ( | ||
reverse( | ||
"api-v1:projects:project-features-list", args=[dynamo_enabled_project.id] | ||
), | ||
dynamo_enabled_project_environment_one.id, | ||
) | ||
mock_get_overrides_data = mocker.patch("features.views.get_overrides_data") | ||
mock_get_overrides_data.return_value = { | ||
feature.id: EnvironmentFeatureOverridesData() | ||
} | ||
|
||
# When | ||
response = admin_client_new.get(url) | ||
|
||
# Then | ||
assert response.status_code == status.HTTP_200_OK | ||
mock_get_overrides_data.assert_called_once_with( | ||
dynamo_enabled_project_environment_one, | ||
[feature.id], | ||
) |
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.
This test seems to fail in CI. Also, in addition to a mocked unit test, I would expect to see a functional test that places actual data in fixtured Dynamo tables.
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've created a functional test with the Dynamo fixtured tables.
KeyConditionExpression=Key(ENVIRONMENTS_V2_PARTITION_KEY).eq( | ||
str(environment_id), | ||
) | ||
& Key(ENVIRONMENTS_V2_SORT_KEY).begins_with( | ||
get_environments_v2_identity_override_document_key( | ||
feature_id=feature_id, | ||
), | ||
) | ||
) |
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.
nit: The query is identical to the one used in get_identity_overrides_by_environment_id
method — maybe it makes sense to DRY this out.
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.
Sure, sounds good. Updated.
) | ||
|
||
results = [] | ||
for future in as_completed(futures): |
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 don't think I understand the need to use as_completed
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.
It's just to process results as they become available rather than waiting for all tasks to finish before proceeding.
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.
This doesn't do anything as all the submitted tasks are completed by the time of this call.
The tasks are done by implicitly calling ThreadPoolExecutor.__exit__
as you exit from the with ThreadPoolExecutor()
block.
…h/flagsmith into fix/speed_up_identity_overrides
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## main #4840 +/- ##
=======================================
Coverage 97.35% 97.35%
=======================================
Files 1182 1183 +1
Lines 41272 41344 +72
=======================================
+ Hits 40179 40251 +72
Misses 1093 1093 ☔ View full report in Codecov by Sentry. 🚨 Try these New Features:
|
Changes
This changes the way that identity overrides are requested for the features page in two ways. First, instead of the entire document getting paginated and returned to the client, only one page per feature is returned. Second, instead of every feature being returned in the network call, we only serve up the features for the current page.
How did you test this code?
I've written one new test, although I'm awaiting codecov to let me know where else needs testing, since I'm sure some are missing.