Skip to content

Commit c13fbca

Browse files
authored
perf: move assemble status to redis (#70414)
This PR reverts the reverted revert, since https://github.com/getsentry/getsentry/commit/6a76fde9a0988cc619bd3fbef5f8910a3f30a9c2 landed on Getsentry.
1 parent d4989ed commit c13fbca

File tree

5 files changed

+65
-4
lines changed

5 files changed

+65
-4
lines changed

src/sentry/conf/server.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ def env(
135135
SENTRY_METRIC_META_REDIS_CLUSTER = "default"
136136
SENTRY_ESCALATION_THRESHOLDS_REDIS_CLUSTER = "default"
137137
SENTRY_SPAN_BUFFER_CLUSTER = "default"
138+
SENTRY_ASSEMBLE_CLUSTER = "default"
138139

139140
# Hosts that are allowed to use system token authentication.
140141
# http://en.wikipedia.org/wiki/Reserved_IP_addresses

src/sentry/options/defaults.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2300,6 +2300,10 @@
23002300
flags=FLAG_BOOL | FLAG_AUTOMATOR_MODIFIABLE,
23012301
)
23022302

2303+
2304+
# Switch to read assemble status from Redis instead of memcache
2305+
register("assemble.read_from_redis", default=False, flags=FLAG_AUTOMATOR_MODIFIABLE)
2306+
23032307
# Sampling rates for testing Rust-based grouping enhancers
23042308

23052309
# Rate at which to run the Rust implementation of `assemble_stacktrace_component`

src/sentry/tasks/assemble.py

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@
66
from abc import ABC, abstractmethod
77
from datetime import datetime
88
from os import path
9-
from typing import IO, Generic, NamedTuple, Protocol, TypeVar
9+
from typing import IO, TYPE_CHECKING, Generic, NamedTuple, Protocol, TypeVar
1010

11+
import orjson
1112
import sentry_sdk
13+
from django.conf import settings
1214
from django.db import IntegrityError, router
1315
from django.db.models import Q
1416
from django.utils import timezone
@@ -39,13 +41,16 @@
3941
from sentry.models.releasefile import ReleaseArchive, ReleaseFile, update_artifact_index
4042
from sentry.silo.base import SiloMode
4143
from sentry.tasks.base import instrumented_task
42-
from sentry.utils import metrics
44+
from sentry.utils import metrics, redis
4345
from sentry.utils.db import atomic_transaction
4446
from sentry.utils.files import get_max_file_size
4547
from sentry.utils.sdk import bind_organization_context, configure_scope
4648

4749
logger = logging.getLogger(__name__)
4850

51+
if TYPE_CHECKING:
52+
from rediscluster import RedisCluster
53+
4954

5055
class ChunkFileState:
5156
OK = "ok" # File in database
@@ -164,12 +169,18 @@ def _get_cache_key(task, scope, checksum):
164169
% (
165170
str(scope).encode("ascii"),
166171
checksum.encode("ascii"),
167-
str(task).encode("utf-8"),
172+
str(task).encode(),
168173
)
169174
).hexdigest()
170175
)
171176

172177

178+
def _get_redis_cluster_for_assemble() -> RedisCluster:
179+
cluster_key = settings.SENTRY_ASSEMBLE_CLUSTER
180+
return redis.redis_clusters.get(cluster_key) # type: ignore[return-value]
181+
182+
183+
@sentry_sdk.tracing.trace
173184
def get_assemble_status(task, scope, checksum):
174185
"""
175186
Checks the current status of an assembling task.
@@ -179,26 +190,42 @@ def get_assemble_status(task, scope, checksum):
179190
notice or error message.
180191
"""
181192
cache_key = _get_cache_key(task, scope, checksum)
182-
rv = default_cache.get(cache_key)
193+
194+
if options.get("assemble.read_from_redis"):
195+
client = _get_redis_cluster_for_assemble()
196+
rv = client.get(cache_key)
197+
198+
# It is stored as bytes with [state, detail] on Redis.
199+
if rv:
200+
rv = orjson.loads(rv)
201+
else:
202+
rv = default_cache.get(cache_key)
203+
183204
if rv is None:
184205
return None, None
185206
return tuple(rv)
186207

187208

209+
@sentry_sdk.tracing.trace
188210
def set_assemble_status(task, scope, checksum, state, detail=None):
189211
"""
190212
Updates the status of an assembling task. It is cached for 10 minutes.
191213
"""
192214
cache_key = _get_cache_key(task, scope, checksum)
193215
default_cache.set(cache_key, (state, detail), 600)
216+
redis_client = _get_redis_cluster_for_assemble()
217+
redis_client.set(name=cache_key, value=orjson.dumps([state, detail]), ex=600)
194218

195219

220+
@sentry_sdk.tracing.trace
196221
def delete_assemble_status(task, scope, checksum):
197222
"""
198223
Deletes the status of an assembling task.
199224
"""
200225
cache_key = _get_cache_key(task, scope, checksum)
201226
default_cache.delete(cache_key)
227+
redis_client = _get_redis_cluster_for_assemble()
228+
redis_client.delete(cache_key)
202229

203230

204231
@instrumented_task(

src/sentry/testutils/helpers/redis.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ def use_redis_cluster(
1212
cluster_id: str = "cluster",
1313
high_watermark: int = 100,
1414
with_settings: dict[str, Any] | None = None,
15+
with_options: dict[str, Any] | None = None,
1516
) -> Generator[None, None, None]:
1617
# Cluster id needs to be different than "default" to distinguish redis instance with redis cluster.
1718

@@ -32,6 +33,9 @@ def use_redis_cluster(
3233
},
3334
}
3435

36+
if with_options:
37+
options.update(with_options)
38+
3539
settings = dict(with_settings or {})
3640
settings["SENTRY_PROCESSING_SERVICES"] = {"redis": {"redis": cluster_id}}
3741

tests/sentry/tasks/test_assemble.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import io
22
import os
3+
import uuid
34
from datetime import UTC, datetime, timedelta
45
from hashlib import sha1
56
from unittest import mock
@@ -28,10 +29,13 @@
2829
assemble_artifacts,
2930
assemble_dif,
3031
assemble_file,
32+
delete_assemble_status,
3133
get_assemble_status,
34+
set_assemble_status,
3235
)
3336
from sentry.testutils.cases import TestCase
3437
from sentry.testutils.helpers.datetime import freeze_time
38+
from sentry.testutils.helpers.redis import use_redis_cluster
3539

3640

3741
class BaseAssembleTest(TestCase):
@@ -1047,3 +1051,24 @@ def test_index_if_needed_with_newer_bundle_already_stored(
10471051
organization_id=self.organization.id,
10481052
artifact_bundles=[(artifact_bundle_1, mock.ANY)],
10491053
)
1054+
1055+
1056+
@use_redis_cluster(with_options={"assemble.read_from_redis": True})
1057+
def test_redis_assemble_status():
1058+
task = AssembleTask.DIF
1059+
project_id = uuid.uuid4().hex
1060+
checksum = uuid.uuid4().hex
1061+
1062+
# If it doesn't exist, it should return correct values.
1063+
assert get_assemble_status(task=task, scope=project_id, checksum=checksum) == (None, None)
1064+
1065+
# Test setter
1066+
set_assemble_status(task, project_id, checksum, ChunkFileState.CREATED, detail="cylons")
1067+
assert get_assemble_status(task=task, scope=project_id, checksum=checksum) == (
1068+
"created",
1069+
"cylons",
1070+
)
1071+
1072+
# Deleting should actually delete it.
1073+
delete_assemble_status(task, project_id, checksum=checksum)
1074+
assert get_assemble_status(task=task, scope=project_id, checksum=checksum) == (None, None)

0 commit comments

Comments
 (0)