Skip to content

Commit b9b17a7

Browse files
authored
perf(aci): Make get_latest_release_for_env cache not-found releases (#96674)
We frequently see slow tasks from repeated latest release condition evaluations. This may be due to the cache not being used when releases aren't found. Adds caching and a metric to observe how much value it is adding. Also adds a typed cache access helper to clarify None vs False, which is a bit overkill for a single use but helpful nonetheless.
1 parent 1103d2e commit b9b17a7

File tree

1 file changed

+52
-6
lines changed

1 file changed

+52
-6
lines changed

src/sentry/workflow_engine/handlers/condition/latest_release_handler.py

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,87 @@
1-
from typing import Any
1+
from abc import abstractmethod
2+
from typing import Any, Literal
23

34
from sentry import tagstore
45
from sentry.eventstore.models import GroupEvent
56
from sentry.models.environment import Environment
67
from sentry.models.release import Release
78
from sentry.rules.filters.latest_release import get_project_release_cache_key
89
from sentry.search.utils import get_latest_release
10+
from sentry.utils import metrics
911
from sentry.utils.cache import cache
1012
from sentry.workflow_engine.models.data_condition import Condition
1113
from sentry.workflow_engine.registry import condition_handler_registry
1214
from sentry.workflow_engine.types import DataConditionHandler, WorkflowEventData
1315

1416

17+
class CacheAccess[T]:
18+
"""
19+
Base class for type-safe naive cache access.
20+
"""
21+
22+
@abstractmethod
23+
def key(self) -> str:
24+
raise NotImplementedError
25+
26+
def get(self) -> T | None:
27+
return cache.get(self.key())
28+
29+
def set(self, value: T, timeout: float | None) -> None:
30+
cache.set(self.key(), value, timeout)
31+
32+
33+
class _LatestReleaseCacheAccess(CacheAccess[Release | Literal[False]]):
34+
"""
35+
If we have a release for a project in an environment, we cache it.
36+
If we don't, we cache False.
37+
"""
38+
39+
def __init__(self, event: GroupEvent, environment: Environment | None):
40+
self._key = get_project_release_cache_key(
41+
event.group.project_id, environment.id if environment else None
42+
)
43+
44+
def key(self) -> str:
45+
return self._key
46+
47+
1548
def get_latest_release_for_env(
1649
environment: Environment | None, event: GroupEvent
1750
) -> Release | None:
18-
cache_key = get_project_release_cache_key(
19-
event.group.project_id, environment.id if environment else None
20-
)
21-
latest_release = cache.get(cache_key)
51+
cache_access = _LatestReleaseCacheAccess(event, environment)
52+
latest_release = cache_access.get()
2253
if latest_release is not None:
54+
if latest_release is False:
55+
return None
2356
return latest_release
2457

2558
organization_id = event.group.project.organization_id
2659
environments = [environment] if environment else None
60+
61+
def record_get_latest_release_result(result: str) -> None:
62+
metrics.incr(
63+
"workflow_engine.latest_release.get_latest_release",
64+
tags={
65+
"has_environment": str(environment is not None),
66+
"result": result,
67+
},
68+
)
69+
2770
try:
2871
latest_release_version = get_latest_release(
2972
[event.group.project],
3073
environments,
3174
organization_id,
3275
)[0]
76+
record_get_latest_release_result("success")
3377
except Release.DoesNotExist:
78+
record_get_latest_release_result("does_not_exist")
79+
cache_access.set(False, 600)
3480
return None
3581
latest_release = Release.objects.get(
3682
version=latest_release_version, organization_id=organization_id
3783
)
38-
cache.set(cache_key, latest_release or False, 600)
84+
cache_access.set(latest_release or False, 600)
3985
return latest_release
4086

4187

0 commit comments

Comments
 (0)