Skip to content
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

Open
wants to merge 18 commits into
base: main
Choose a base branch
from

Conversation

zachaysan
Copy link
Contributor

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.

@zachaysan zachaysan requested a review from a team as a code owner November 18, 2024 14:17
@zachaysan zachaysan requested review from khvn26 and removed request for a team November 18, 2024 14:17
Copy link

vercel bot commented Nov 18, 2024

The latest updates on your projects. Learn more about Vercel for Git ↗︎

3 Skipped Deployments
Name Status Preview Comments Updated (UTC)
docs ⬜️ Ignored (Inspect) Visit Preview Nov 19, 2024 7:34pm
flagsmith-frontend-preview ⬜️ Ignored (Inspect) Visit Preview Nov 19, 2024 7:34pm
flagsmith-frontend-staging ⬜️ Ignored (Inspect) Visit Preview Nov 19, 2024 7:34pm

@github-actions github-actions bot added api Issue related to the REST API fix labels Nov 18, 2024
Copy link
Contributor

github-actions bot commented Nov 18, 2024

Docker builds report

Image Build Status Security report
ghcr.io/flagsmith/flagsmith-api-test:pr-4840 Finished ✅ Skipped
ghcr.io/flagsmith/flagsmith-frontend:pr-4840 Finished ✅ Results
ghcr.io/flagsmith/flagsmith-api:pr-4840 Finished ✅ Results
ghcr.io/flagsmith/flagsmith:pr-4840 Finished ✅ Results
ghcr.io/flagsmith/flagsmith-private-cloud:pr-4840 Finished ✅ Results

Copy link
Contributor

github-actions bot commented Nov 18, 2024

Uffizzi Ephemeral Environment deployment-58332

☁️ https://app.uffizzi.com/github.com/Flagsmith/flagsmith/pull/4840

📄 View Application Logs etc.

What is Uffizzi? Learn more!

Copy link
Member

@khvn26 khvn26 left a 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.

Comment on lines 70 to 78
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"
Copy link
Member

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.

Copy link
Contributor Author

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.

Comment on lines 101 to 105
executor.submit(
self.get_page_of_feature_identities,
environment_id,
feature_id,
)
Copy link
Member

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.

Copy link
Contributor Author

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(
Copy link
Member

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.

Suggested change
def get_page_of_feature_identities(
def get_identity_overrides_page(

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea. Updated.

Comment on lines +2066 to +2095
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],
)
Copy link
Member

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.

Copy link
Contributor Author

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.

Comment on lines 123 to 131
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,
),
)
)
Copy link
Member

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.

Copy link
Contributor Author

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):
Copy link
Member

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.

Copy link
Contributor Author

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.

Copy link
Member

@khvn26 khvn26 Nov 19, 2024

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.

@github-actions github-actions bot added fix and removed fix labels Nov 18, 2024
@github-actions github-actions bot added fix and removed fix labels Nov 19, 2024
Copy link

codecov bot commented Nov 19, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 97.35%. Comparing base (9bbfdf0) to head (3e2c959).
Report is 10 commits behind head on main.

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.
📢 Have feedback on the report? Share it here.


🚨 Try these New Features:

@github-actions github-actions bot added fix and removed fix labels Nov 19, 2024
@github-actions github-actions bot added fix and removed fix labels Nov 19, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
api Issue related to the REST API fix
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants