Skip to content

Commit 2d87621

Browse files
authored
chore(hybrid-cloud): Break queries that depend on cross silo FKs (#45095)
Separating out the query changes from https://github.com/getsentry/sentry/pull/44595/files Hybrid Cloud needs to break many foreign key relationships that depend on cross silo models. To support this, we have a new column which acts merely as a big int referencing identifiers, but uses a eventually consistent system to cascade or set null when deletions happen across silos. This PR first changes all known api and test usages of several cross silo foreign keys in preparation for the migration that actually breaks those foreign keys. Several more to come.
1 parent d546626 commit 2d87621

File tree

56 files changed

+444
-359
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+444
-359
lines changed

src/sentry/api/endpoints/project_transaction_threshold.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ def post(self, request: Request, project) -> Response:
9090
)
9191
project_threshold.threshold = data.get("threshold") or project_threshold.threshold
9292
project_threshold.metric = data.get("metric") or project_threshold.metric
93-
project_threshold.edited_by = request.user
93+
project_threshold.edited_by_id = request.user.id
9494
project_threshold.save()
9595

9696
created = False
@@ -101,7 +101,7 @@ def post(self, request: Request, project) -> Response:
101101
organization=project.organization,
102102
threshold=data.get("threshold", 300),
103103
metric=data.get("metric", TransactionMetric.DURATION.value),
104-
edited_by=request.user,
104+
edited_by_id=request.user.id,
105105
)
106106

107107
created = True

src/sentry/api/endpoints/project_transaction_threshold_override.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ def post(self, request: Request, organization) -> Response:
117117
defaults={
118118
"threshold": data["threshold"],
119119
"metric": data["metric"],
120-
"edited_by": request.user,
120+
"edited_by_id": request.user.id,
121121
},
122122
)
123123

src/sentry/api/helpers/group_index/update.py

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
from sentry.models.grouphistory import record_group_history_from_activity_type
5050
from sentry.models.groupinbox import GroupInboxRemoveAction, add_group_to_inbox
5151
from sentry.notifications.types import SUBSCRIPTION_REASON_MAP, GroupSubscriptionReason
52-
from sentry.services.hybrid_cloud.user import RpcUser
52+
from sentry.services.hybrid_cloud.user import RpcUser, user_service
5353
from sentry.signals import (
5454
issue_ignored,
5555
issue_mark_reviewed,
@@ -61,7 +61,6 @@
6161
from sentry.tasks.merge import merge_groups
6262
from sentry.types.activity import ActivityType
6363
from sentry.utils import metrics
64-
from sentry.utils.functional import extract_lazy_object
6564

6665
from . import ACTIVITIES_COUNT, BULK_MUTATION_LIMIT, SearchFunction, delete_group_list
6766
from .validators import GroupValidator, ValidationError
@@ -280,10 +279,15 @@ def update_groups(
280279
# no version yet
281280
"version": ""
282281
}
282+
283+
serialized_user = user_service.serialize_many(
284+
filter=dict(user_ids=[user.id]), as_user=user
285+
)
283286
status_details = {
284287
"inNextRelease": True,
285-
"actor": serialize(extract_lazy_object(user), user),
286288
}
289+
if serialized_user:
290+
status_details["actor"] = serialized_user[0]
287291
res_type = GroupResolution.Type.in_next_release
288292
res_type_str = "in_next_release"
289293
res_status = GroupResolution.Status.pending
@@ -301,10 +305,15 @@ def update_groups(
301305
# no version yet
302306
"version": release.version
303307
}
308+
309+
serialized_user = user_service.serialize_many(
310+
filter=dict(user_ids=[user.id]), as_user=user
311+
)
304312
status_details = {
305313
"inRelease": release.version,
306-
"actor": serialize(extract_lazy_object(user), user),
307314
}
315+
if serialized_user:
316+
status_details["actor"] = serialized_user[0]
308317
res_type = GroupResolution.Type.in_release
309318
res_type_str = "in_release"
310319
res_status = GroupResolution.Status.resolved
@@ -318,10 +327,15 @@ def update_groups(
318327
commit = statusDetails["inCommit"]
319328
activity_type = ActivityType.SET_RESOLVED_IN_COMMIT.value
320329
activity_data = {"commit": commit.id}
330+
serialized_user = user_service.serialize_many(
331+
filter=dict(user_ids=[user.id]), as_user=user
332+
)
333+
321334
status_details = {
322335
"inCommit": serialize(commit, user),
323-
"actor": serialize(extract_lazy_object(user), user),
324336
}
337+
if serialized_user:
338+
status_details["actor"] = serialized_user[0]
325339
res_type_str = "in_commit"
326340
else:
327341
res_type_str = "now"
@@ -572,14 +586,18 @@ def update_groups(
572586
"actor_id": user.id if user.is_authenticated else None,
573587
},
574588
)
589+
serialized_user = user_service.serialize_many(
590+
filter=dict(user_ids=[user.id]), as_user=user
591+
)
575592
result["statusDetails"] = {
576593
"ignoreCount": ignore_count,
577594
"ignoreUntil": ignore_until,
578595
"ignoreUserCount": ignore_user_count,
579596
"ignoreUserWindow": ignore_user_window,
580597
"ignoreWindow": ignore_window,
581-
"actor": serialize(extract_lazy_object(user), user),
582598
}
599+
if serialized_user:
600+
result["statusDetails"]["actor"] = serialized_user[0]
583601
else:
584602
GroupSnooze.objects.filter(group__in=group_ids).delete()
585603
ignore_until = None

src/sentry/api/serializers/models/alert_rule.py

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from collections import defaultdict
2+
from typing import MutableMapping
23

34
from django.db.models import Max, prefetch_related_objects
45

@@ -23,6 +24,7 @@
2324
fetch_actors_by_actor_ids,
2425
)
2526
from sentry.services.hybrid_cloud.app import app_service
27+
from sentry.services.hybrid_cloud.user import RpcUser, user_service
2628
from sentry.snuba.models import SnubaQueryEventType
2729

2830

@@ -70,26 +72,32 @@ def get_attrs(self, item_list, user, **kwargs):
7072
rule_result = result[alert_rules[alert_rule_id]].setdefault("projects", [])
7173
rule_result.append(project_slug)
7274

73-
for rule_activity in AlertRuleActivity.objects.filter(
74-
alert_rule__in=item_list, type=AlertRuleActivityType.CREATED.value
75-
).select_related("alert_rule", "user"):
76-
if rule_activity.user:
77-
user = {
78-
"id": rule_activity.user.id,
79-
"name": rule_activity.user.get_display_name(),
80-
"email": rule_activity.user.email,
81-
}
75+
rule_activities = list(
76+
AlertRuleActivity.objects.filter(
77+
alert_rule__in=item_list, type=AlertRuleActivityType.CREATED.value
78+
)
79+
)
80+
81+
use_by_user_id: MutableMapping[int, RpcUser] = {
82+
user.id: user
83+
for user in user_service.get_many(
84+
filter=dict(user_ids=[r.user_id for r in rule_activities])
85+
)
86+
}
87+
for rule_activity in rule_activities:
88+
rpc_user = use_by_user_id.get(rule_activity.user_id)
89+
if rpc_user:
90+
user = dict(id=rpc_user.id, name=rpc_user.get_display_name(), email=rpc_user.email)
8291
else:
8392
user = None
93+
result[alert_rules[rule_activity.alert_rule_id]]["created_by"] = user
8494

85-
result[alert_rules[rule_activity.alert_rule.id]].update({"created_by": user})
86-
87-
resolved_actors = {}
8895
owners_by_type = defaultdict(list)
8996
for item in item_list:
9097
if item.owner_id is not None:
9198
owners_by_type[actor_type_to_string(item.owner.type)].append(item.owner_id)
9299

100+
resolved_actors = {}
93101
for k, v in ACTOR_TYPES.items():
94102
resolved_actors[k] = {
95103
a.actor_id: a.id

src/sentry/api/serializers/models/discoversavedquery.py

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,28 @@
11
from collections import defaultdict
22

3-
from django.db.models.query import prefetch_related_objects
4-
5-
from sentry.api.serializers import Serializer, register, serialize
6-
from sentry.api.serializers.models.user import UserSerializer
3+
from sentry.api.serializers import Serializer, register
74
from sentry.constants import ALL_ACCESS_PROJECTS
85
from sentry.discover.models import DiscoverSavedQuery
6+
from sentry.services.hybrid_cloud.user import user_service
97
from sentry.utils.dates import outside_retention_with_modified_start, parse_timestamp
108

119

1210
@register(DiscoverSavedQuery)
1311
class DiscoverSavedQuerySerializer(Serializer):
1412
def get_attrs(self, item_list, user):
15-
prefetch_related_objects(item_list, "created_by")
16-
1713
result = defaultdict(lambda: {"created_by": {}})
1814

19-
user_serializer = UserSerializer()
20-
serialized_users = {
21-
user["id"]: user
22-
for user in serialize(
23-
[
24-
discover_saved_query.created_by
15+
service_serialized = user_service.serialize_many(
16+
filter={
17+
"user_ids": [
18+
discover_saved_query.created_by_id
2519
for discover_saved_query in item_list
26-
if discover_saved_query.created_by
27-
],
28-
user=user,
29-
serializer=user_serializer,
30-
)
31-
}
20+
if discover_saved_query.created_by_id
21+
]
22+
},
23+
as_user=user,
24+
)
25+
serialized_users = {user["id"]: user for user in service_serialized}
3226

3327
for discover_saved_query in item_list:
3428
result[discover_saved_query]["created_by"] = serialized_users.get(

src/sentry/api/serializers/models/exporteddata.py

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,31 @@
1-
from sentry.api.serializers import Serializer, register, serialize
1+
from sentry.api.serializers import Serializer, register
22
from sentry.data_export.base import ExportQueryType
33
from sentry.data_export.models import ExportedData
4-
from sentry.models import User
4+
from sentry.services.hybrid_cloud.user import user_service
55

66

77
@register(ExportedData)
88
class ExportedDataSerializer(Serializer):
99
def get_attrs(self, item_list, user, **kwargs):
1010
attrs = {}
11-
users = User.objects.filter(id__in={item.user_id for item in item_list})
12-
user_lookup = {user.id: user for user in users}
11+
serialized_users = {
12+
u["id"]: u
13+
for u in user_service.serialize_many(
14+
filter=dict(user_ids=[item.user_id for item in item_list])
15+
)
16+
}
1317
for item in item_list:
14-
user = user_lookup[item.user_id]
15-
serialized_user = serialize(user)
16-
attrs[item] = {
17-
"user": {
18-
"id": serialized_user["id"],
19-
"email": serialized_user["email"],
20-
"username": serialized_user["username"],
18+
if str(item.user_id) in serialized_users:
19+
serialized_user = serialized_users[str(item.user_id)]
20+
attrs[item] = {
21+
"user": {
22+
"id": serialized_user["id"],
23+
"email": serialized_user["email"],
24+
"username": serialized_user["username"],
25+
}
2126
}
22-
}
27+
else:
28+
attrs[item] = {}
2329
return attrs
2430

2531
def serialize(self, obj, attrs, user, **kwargs):
@@ -33,7 +39,7 @@ def serialize(self, obj, attrs, user, **kwargs):
3339

3440
return {
3541
"id": obj.id,
36-
"user": attrs["user"],
42+
"user": attrs.get("user"),
3743
"dateCreated": obj.date_added,
3844
"dateFinished": obj.date_finished,
3945
"dateExpired": obj.date_expired,

src/sentry/api/serializers/models/group.py

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
Optional,
1616
Protocol,
1717
Sequence,
18+
Set,
1819
Tuple,
1920
TypedDict,
2021
Union,
@@ -35,7 +36,6 @@
3536
from sentry.constants import LOG_LEVELS
3637
from sentry.issues.grouptype import GroupCategory
3738
from sentry.models import (
38-
ActorTuple,
3939
Commit,
4040
Environment,
4141
Group,
@@ -183,19 +183,32 @@ def __init__(
183183
self.collapse = collapse
184184
self.expand = expand
185185

186-
def _serialize_assigness(
187-
self, actor_dict: Mapping[int, ActorTuple]
188-
) -> Mapping[int, Union[Team, Any]]:
189-
actors_by_type: MutableMapping[Any, List[ActorTuple]] = defaultdict(list)
190-
for actor in actor_dict.values():
191-
actors_by_type[actor.type].append(actor)
186+
def _serialize_assignees(self, item_list: Sequence[Group]) -> Mapping[int, Union[Team, Any]]:
187+
gas = GroupAssignee.objects.filter(group__in=item_list)
188+
result: MutableMapping[int, Union[Team, Any]] = {}
189+
all_team_ids: MutableMapping[int, Set[int]] = {}
190+
all_user_ids: MutableMapping[int, Set[int]] = {}
191+
192+
for g in gas:
193+
if g.team_id:
194+
if g.team_id not in all_team_ids:
195+
all_team_ids[g.team_id] = {g.group_id}
196+
else:
197+
all_team_ids[g.team_id].add(g.group_id)
198+
if g.user_id:
199+
if g.team_id not in all_team_ids:
200+
all_user_ids[g.user_id] = {g.group_id}
201+
else:
202+
all_user_ids[g.user_id].add(g.group_id)
203+
204+
for team in Team.objects.filter(id__in=all_team_ids.keys()):
205+
for group_id in all_team_ids[team.id]:
206+
result[group_id] = team
207+
for user in user_service.get_many(filter=dict(user_ids=list(all_user_ids.keys()))):
208+
for group_id in all_user_ids[user.id]:
209+
result[group_id] = user
192210

193-
resolved_actors = {}
194-
for t, actors in actors_by_type.items():
195-
serializable = ActorTuple.resolve_many(actors)
196-
resolved_actors[t] = {actor.id: actor for actor in serializable}
197-
198-
return {key: resolved_actors[value.type][value.id] for key, value in actor_dict.items()}
211+
return result
199212

200213
def get_attrs(
201214
self, item_list: Sequence[Group], user: Any, **kwargs: Any
@@ -223,11 +236,7 @@ def get_attrs(
223236
seen_groups = {}
224237
subscriptions = defaultdict(lambda: (False, False, None))
225238

226-
assignees: Mapping[int, ActorTuple] = {
227-
a.group_id: a.assigned_actor()
228-
for a in GroupAssignee.objects.filter(group__in=item_list)
229-
}
230-
resolved_assignees = self._serialize_assigness(assignees)
239+
resolved_assignees = self._serialize_assignees(item_list)
231240

232241
ignore_items = {g.group_id: g for g in GroupSnooze.objects.filter(group__in=item_list)}
233242

src/sentry/api/serializers/models/incident.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,7 @@ def get_attrs(self, item_list, user, **kwargs):
4444

4545
if "seen_by" in self.expand:
4646
incident_seen_list = list(
47-
IncidentSeen.objects.filter(incident__in=item_list)
48-
.select_related("user")
49-
.order_by("-last_seen")
47+
IncidentSeen.objects.filter(incident__in=item_list).order_by("-last_seen")
5048
)
5149
incident_seen_dict = defaultdict(list)
5250
for incident_seen, serialized_seen_by in zip(
@@ -108,9 +106,9 @@ def get_attrs(self, item_list, user, **kwargs):
108106
subscribed_incidents = set()
109107
if user.is_authenticated:
110108
subscribed_incidents = set(
111-
IncidentSubscription.objects.filter(incident__in=item_list, user=user).values_list(
112-
"incident_id", flat=True
113-
)
109+
IncidentSubscription.objects.filter(
110+
incident__in=item_list, user_id=user.id
111+
).values_list("incident_id", flat=True)
114112
)
115113

116114
for item in item_list:

src/sentry/api/serializers/models/incidentactivity.py

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,16 @@
11
from django.db.models import prefetch_related_objects
22

3-
from sentry.api.serializers import Serializer, register, serialize
4-
from sentry.api.serializers.models.user import UserSerializer
3+
from sentry.api.serializers import Serializer, register
54
from sentry.incidents.models import IncidentActivity
5+
from sentry.services.hybrid_cloud.user import user_service
66

77

88
@register(IncidentActivity)
99
class IncidentActivitySerializer(Serializer):
1010
def get_attrs(self, item_list, user, **kwargs):
1111
prefetch_related_objects(item_list, "incident__organization")
12-
prefetch_related_objects(item_list, "user")
13-
user_serializer = UserSerializer()
14-
serialized_users = serialize(
15-
{item.user for item in item_list if item.user_id},
16-
user=user,
17-
serializer=user_serializer,
12+
serialized_users = user_service.serialize_many(
13+
filter={"user_ids": [i.user_id for i in item_list if i.user_id]}, as_user=user
1814
)
1915
user_lookup = {user["id"]: user for user in serialized_users}
2016
return {item: {"user": user_lookup.get(str(item.user_id))} for item in item_list}

0 commit comments

Comments
 (0)