diff --git a/src/sentry/hybridcloud/rpc/filter_query.py b/src/sentry/hybridcloud/rpc/filter_query.py index 5ec01fc1a7701c..cf843eecf7d9bd 100644 --- a/src/sentry/hybridcloud/rpc/filter_query.py +++ b/src/sentry/hybridcloud/rpc/filter_query.py @@ -109,6 +109,7 @@ def serialize_many( as_user: RpcUser | None = None, auth_context: AuthenticationContext | None = None, serializer: SERIALIZER_ENUM | None = None, + select_related: bool = True, ) -> list[OpaqueSerializedResponse]: from sentry.api.serializers import serialize from sentry.users.services.user import RpcUser @@ -121,7 +122,7 @@ def serialize_many( if as_user is None and auth_context: as_user = auth_context.user - result = self.query_many(filter=filter) + result = self.query_many(filter=filter, select_related=select_related) return serialize( list(result), user=as_user, diff --git a/src/sentry/users/services/user/impl.py b/src/sentry/users/services/user/impl.py index c0c60eb9f3aaad..e69eba78fd5be0 100644 --- a/src/sentry/users/services/user/impl.py +++ b/src/sentry/users/services/user/impl.py @@ -62,7 +62,9 @@ def serialize_many( auth_context: AuthenticationContext | None = None, serializer: UserSerializeType | None = None, ) -> list[OpaqueSerializedResponse]: - return self._FQ.serialize_many(filter, as_user, auth_context, serializer) + return self._FQ.serialize_many( + filter, as_user, auth_context, serializer, select_related=False + ) def get_many(self, *, filter: UserFilterArgs) -> list[RpcUser]: return self._FQ.get_many(filter) diff --git a/tests/sentry/users/services/test_user.py b/tests/sentry/users/services/test_user.py index 7f9fd83877d23a..17ef8444e218ea 100644 --- a/tests/sentry/users/services/test_user.py +++ b/tests/sentry/users/services/test_user.py @@ -1,3 +1,6 @@ +from django.db import connections +from django.test.utils import CaptureQueriesContext + from sentry.silo.base import SiloMode from sentry.testutils.cases import TestCase from sentry.testutils.silo import all_silo_test, assume_test_silo_mode @@ -74,6 +77,19 @@ def test_add_permission(self) -> None: created = user_service.add_permission(user_id=self.user.id, permission="superuser.write") assert created is False + def test_serialize_many_avoids_correlated_subqueries(self) -> None: + """base_query() eagerly loads data via correlated subqueries for the + get_many/serialize_rpc path. serialize_many should not pay for these + since the API serializer re-fetches what it needs in get_attrs().""" + with assume_test_silo_mode(SiloMode.CONTROL): + user_service.serialize_many(filter={"user_ids": [self.user.id]}) + + with CaptureQueriesContext(connections["control"]) as ctx: + user_service.serialize_many(filter={"user_ids": [self.user.id]}) + + all_sql = " ".join(q["sql"] for q in ctx.captured_queries) + assert "array_agg" not in all_sql + def test_remove_permission(self) -> None: # Create a permission first with assume_test_silo_mode(SiloMode.CONTROL):