From 1d2cd49f91f41de71275e6f545f0ff7952cad162 Mon Sep 17 00:00:00 2001 From: Rachel Chen Date: Sat, 1 Nov 2025 18:49:58 -0700 Subject: [PATCH 01/11] idk --- .../v1/test_trace_item_attribute_values_v1.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/web/rpc/v1/test_trace_item_attribute_values_v1.py b/tests/web/rpc/v1/test_trace_item_attribute_values_v1.py index 2e226281feb..f02088b2ac8 100644 --- a/tests/web/rpc/v1/test_trace_item_attribute_values_v1.py +++ b/tests/web/rpc/v1/test_trace_item_attribute_values_v1.py @@ -108,6 +108,12 @@ def setup_teardown(clickhouse_db: None, redis_db: None) -> Generator[List[bytes] "sentry.transaction": AnyValue(string_value="*foo"), }, ), + gen_item_message( + start_timestamp=start_timestamp, + attributes={ + "metric.questions.6._id": AnyValue(string_value="jlfsj"), + }, + ), ] write_raw_unprocessed_events(items_storage, messages) # type: ignore yield messages @@ -181,3 +187,13 @@ def test_item_id_substring_match(self, setup_teardown: List[bytes]) -> None: ) res = AttributeValuesRequest().execute(req) assert res.values == [item_id] + + def test_attribute_names_with_dots(self, setup_teardown: Any) -> None: + message = TraceItemAttributeValuesRequest( + meta=COMMON_META, + limit=10, + key=AttributeKey(name="metric.questions.6._id", type=AttributeKey.TYPE_STRING), + ) + response = AttributeValuesRequest().execute(message) + assert len(response.values) == 1 + assert set(response.values) == {"jlfsj"} From f85a64457b9e4f60c377cc3aa59253d644595c51 Mon Sep 17 00:00:00 2001 From: Rachel Chen Date: Sun, 9 Nov 2025 15:32:27 -0800 Subject: [PATCH 02/11] feat(cbrs): load based routing strategy --- .../routing_strategies/load_based.py | 62 ++++++++++ .../v1/routing_strategies/test_load_based.py | 111 ++++++++++++++++++ 2 files changed, 173 insertions(+) create mode 100644 snuba/web/rpc/storage_routing/routing_strategies/load_based.py create mode 100644 tests/web/rpc/v1/routing_strategies/test_load_based.py diff --git a/snuba/web/rpc/storage_routing/routing_strategies/load_based.py b/snuba/web/rpc/storage_routing/routing_strategies/load_based.py new file mode 100644 index 00000000000..7a680cde973 --- /dev/null +++ b/snuba/web/rpc/storage_routing/routing_strategies/load_based.py @@ -0,0 +1,62 @@ +import sentry_sdk + +from snuba.configs.configuration import Configuration +from snuba.web.rpc.storage_routing.routing_strategies.outcomes_based import ( + OutcomesBasedRoutingStrategy, +) +from snuba.web.rpc.storage_routing.routing_strategies.storage_routing import ( + RoutingDecision, +) + + +class LoadBasedRoutingStrategy(OutcomesBasedRoutingStrategy): + """ + If cluster load is under a threshold, ignore recommendations and allow the query to pass through with the tier decided based on outcomes-based routing. + """ + + def _additional_config_definitions(self) -> list[Configuration]: + return [ + Configuration( + name="pass_through_load_percentage", + description="If cluster load is below this percentage, allow the query to run regardless of allocation policies", + value_type=int, + default=20, + ), + Configuration( + name="pass_through_max_threads", + description="Max threads to use when allowing the query to pass through under low load", + value_type=int, + default=10, + ), + ] + + def _update_routing_decision( + self, + routing_decision: RoutingDecision, + ) -> None: + super()._update_routing_decision(routing_decision) + + load_info = routing_decision.routing_context.cluster_load_info + if load_info is None: + return + + pass_through_threshold = int(self.get_config_value("pass_through_load_percentage")) + pass_through_max_threads = int(self.get_config_value("pass_through_max_threads")) + + if load_info.cluster_load < pass_through_threshold: + routing_decision.can_run = True + routing_decision.is_throttled = False + routing_decision.clickhouse_settings["max_threads"] = pass_through_max_threads + routing_decision.routing_context.extra_info["load_based_pass_through"] = { + "cluster_load": load_info.cluster_load, + "threshold": pass_through_threshold, + "max_threads": pass_through_max_threads, + } + sentry_sdk.update_current_span( # pyright: ignore[reportUndefinedVariable] + attributes={ + "load_based_pass_through": True, + "cluster_load": load_info.cluster_load, + "pass_through_threshold": pass_through_threshold, + "pass_through_max_threads": pass_through_max_threads, + } + ) diff --git a/tests/web/rpc/v1/routing_strategies/test_load_based.py b/tests/web/rpc/v1/routing_strategies/test_load_based.py new file mode 100644 index 00000000000..1c08f90f72a --- /dev/null +++ b/tests/web/rpc/v1/routing_strategies/test_load_based.py @@ -0,0 +1,111 @@ +import uuid +from datetime import UTC, datetime, timedelta +from unittest.mock import patch + +import pytest +from google.protobuf.timestamp_pb2 import Timestamp +from sentry_protos.snuba.v1.downsampled_storage_pb2 import DownsampledStorageConfig +from sentry_protos.snuba.v1.endpoint_trace_item_table_pb2 import TraceItemTableRequest +from sentry_protos.snuba.v1.request_common_pb2 import RequestMeta, TraceItemType + +from snuba.configs.configuration import Configuration, ResourceIdentifier +from snuba.datasets.storages.storage_key import StorageKey +from snuba.query.allocation_policies import ( + MAX_THRESHOLD, + NO_SUGGESTION, + NO_UNITS, + AllocationPolicy, + QueryResultOrError, + QuotaAllowance, +) +from snuba.utils.metrics.timer import Timer +from snuba.web.rpc.storage_routing.load_retriever import LoadInfo +from snuba.web.rpc.storage_routing.routing_strategies.load_based import ( + LoadBasedRoutingStrategy, +) +from snuba.web.rpc.storage_routing.routing_strategies.storage_routing import ( + BaseRoutingStrategy, + RoutingContext, +) + +BASE_TIME = datetime.now(UTC).replace(hour=0, minute=0, second=0, microsecond=0) +_PROJECT_ID = 1 +_ORG_ID = 1 + + +def _get_request_meta(hour_interval: int = 1) -> RequestMeta: + start = BASE_TIME - timedelta(hours=hour_interval) + end = BASE_TIME + return RequestMeta( + project_ids=[_PROJECT_ID], + organization_id=_ORG_ID, + cogs_category="something", + referrer="something", + start_timestamp=Timestamp(seconds=int(start.timestamp())), + end_timestamp=Timestamp(seconds=int(end.timestamp())), + trace_item_type=TraceItemType.TRACE_ITEM_TYPE_SPAN, + downsampled_storage_config=DownsampledStorageConfig( + mode=DownsampledStorageConfig.MODE_NORMAL + ), + ) + + +@pytest.mark.clickhouse_db +@pytest.mark.redis_db +def test_load_based_routing_pass_through_even_if_policies_reject() -> None: + # policy that always rejects (can_run=False) + class RejectAllPolicy(AllocationPolicy): + def _additional_config_definitions(self) -> list[Configuration]: + return [] + + def _get_quota_allowance( + self, tenant_ids: dict[str, str | int], query_id: str + ) -> QuotaAllowance: + return QuotaAllowance( + can_run=False, + max_threads=0, + explanation={"reason": "reject all"}, + is_throttled=True, + throttle_threshold=MAX_THRESHOLD, + rejection_threshold=MAX_THRESHOLD, + quota_used=0, + quota_unit=NO_UNITS, + suggestion=NO_SUGGESTION, + ) + + def _update_quota_balance( + self, + tenant_ids: dict[str, str | int], + query_id: str, + result_or_error: QueryResultOrError, + ) -> None: + return + + strategy = LoadBasedRoutingStrategy() + request = TraceItemTableRequest(meta=_get_request_meta(hour_interval=1)) + context = RoutingContext( + in_msg=request, + timer=Timer("test"), + query_id=uuid.uuid4().hex, + ) + + with patch.object( + BaseRoutingStrategy, + "get_allocation_policies", + return_value=[ + RejectAllPolicy(ResourceIdentifier(StorageKey("doesntmatter")), ["org_id"], {}) + ], + ): + with patch( + "snuba.web.rpc.storage_routing.routing_strategies.storage_routing.get_cluster_loadinfo", + return_value=LoadInfo(cluster_load=5.0, concurrent_queries=1), + ): + routing_decision = strategy.get_routing_decision(context) + + assert routing_decision.can_run is True + assert routing_decision.clickhouse_settings.get("max_threads") == 10 + assert "load_based_pass_through" in routing_decision.routing_context.extra_info + assert ( + routing_decision.routing_context.extra_info["load_based_pass_through"]["cluster_load"] + == 5.0 + ) From 300ca779e44863a38eb2f6cd80f8feccf48a1693 Mon Sep 17 00:00:00 2001 From: Rachel Chen Date: Sun, 9 Nov 2025 15:37:57 -0800 Subject: [PATCH 03/11] revert file --- .../v1/test_trace_item_attribute_values_v1.py | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/tests/web/rpc/v1/test_trace_item_attribute_values_v1.py b/tests/web/rpc/v1/test_trace_item_attribute_values_v1.py index f02088b2ac8..2e226281feb 100644 --- a/tests/web/rpc/v1/test_trace_item_attribute_values_v1.py +++ b/tests/web/rpc/v1/test_trace_item_attribute_values_v1.py @@ -108,12 +108,6 @@ def setup_teardown(clickhouse_db: None, redis_db: None) -> Generator[List[bytes] "sentry.transaction": AnyValue(string_value="*foo"), }, ), - gen_item_message( - start_timestamp=start_timestamp, - attributes={ - "metric.questions.6._id": AnyValue(string_value="jlfsj"), - }, - ), ] write_raw_unprocessed_events(items_storage, messages) # type: ignore yield messages @@ -187,13 +181,3 @@ def test_item_id_substring_match(self, setup_teardown: List[bytes]) -> None: ) res = AttributeValuesRequest().execute(req) assert res.values == [item_id] - - def test_attribute_names_with_dots(self, setup_teardown: Any) -> None: - message = TraceItemAttributeValuesRequest( - meta=COMMON_META, - limit=10, - key=AttributeKey(name="metric.questions.6._id", type=AttributeKey.TYPE_STRING), - ) - response = AttributeValuesRequest().execute(message) - assert len(response.values) == 1 - assert set(response.values) == {"jlfsj"} From 0c56783188eac32f4e4b97eef8b7e4d476e8e7f6 Mon Sep 17 00:00:00 2001 From: Rachel Chen Date: Sun, 9 Nov 2025 22:01:36 -0800 Subject: [PATCH 04/11] fix --- .../rpc/storage_routing/routing_strategies/load_based.py | 1 - tests/web/rpc/v1/routing_strategies/test_load_based.py | 6 ++---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/snuba/web/rpc/storage_routing/routing_strategies/load_based.py b/snuba/web/rpc/storage_routing/routing_strategies/load_based.py index 7a680cde973..a967604cee8 100644 --- a/snuba/web/rpc/storage_routing/routing_strategies/load_based.py +++ b/snuba/web/rpc/storage_routing/routing_strategies/load_based.py @@ -48,7 +48,6 @@ def _update_routing_decision( routing_decision.is_throttled = False routing_decision.clickhouse_settings["max_threads"] = pass_through_max_threads routing_decision.routing_context.extra_info["load_based_pass_through"] = { - "cluster_load": load_info.cluster_load, "threshold": pass_through_threshold, "max_threads": pass_through_max_threads, } diff --git a/tests/web/rpc/v1/routing_strategies/test_load_based.py b/tests/web/rpc/v1/routing_strategies/test_load_based.py index 1c08f90f72a..80bcd6a5c96 100644 --- a/tests/web/rpc/v1/routing_strategies/test_load_based.py +++ b/tests/web/rpc/v1/routing_strategies/test_load_based.py @@ -105,7 +105,5 @@ def _update_quota_balance( assert routing_decision.can_run is True assert routing_decision.clickhouse_settings.get("max_threads") == 10 assert "load_based_pass_through" in routing_decision.routing_context.extra_info - assert ( - routing_decision.routing_context.extra_info["load_based_pass_through"]["cluster_load"] - == 5.0 - ) + assert routing_decision.routing_context.cluster_load_info is not None + assert routing_decision.routing_context.cluster_load_info.cluster_load == 5.0 From f9c5724a2f44532c8578bf19f6573b5c8905aef3 Mon Sep 17 00:00:00 2001 From: Rachel Chen Date: Tue, 2 Dec 2025 13:55:40 -0800 Subject: [PATCH 05/11] chain --- .../routing_strategies/load_based.py | 8 ++---- .../routing_strategies/outcomes_load_based.py | 25 +++++++++++++++++++ 2 files changed, 27 insertions(+), 6 deletions(-) create mode 100644 snuba/web/rpc/storage_routing/routing_strategies/outcomes_load_based.py diff --git a/snuba/web/rpc/storage_routing/routing_strategies/load_based.py b/snuba/web/rpc/storage_routing/routing_strategies/load_based.py index a967604cee8..153e815253b 100644 --- a/snuba/web/rpc/storage_routing/routing_strategies/load_based.py +++ b/snuba/web/rpc/storage_routing/routing_strategies/load_based.py @@ -1,15 +1,13 @@ import sentry_sdk from snuba.configs.configuration import Configuration -from snuba.web.rpc.storage_routing.routing_strategies.outcomes_based import ( - OutcomesBasedRoutingStrategy, -) from snuba.web.rpc.storage_routing.routing_strategies.storage_routing import ( + BaseRoutingStrategy, RoutingDecision, ) -class LoadBasedRoutingStrategy(OutcomesBasedRoutingStrategy): +class LoadBasedRoutingStrategy(BaseRoutingStrategy): """ If cluster load is under a threshold, ignore recommendations and allow the query to pass through with the tier decided based on outcomes-based routing. """ @@ -34,8 +32,6 @@ def _update_routing_decision( self, routing_decision: RoutingDecision, ) -> None: - super()._update_routing_decision(routing_decision) - load_info = routing_decision.routing_context.cluster_load_info if load_info is None: return diff --git a/snuba/web/rpc/storage_routing/routing_strategies/outcomes_load_based.py b/snuba/web/rpc/storage_routing/routing_strategies/outcomes_load_based.py new file mode 100644 index 00000000000..d2fe29f89eb --- /dev/null +++ b/snuba/web/rpc/storage_routing/routing_strategies/outcomes_load_based.py @@ -0,0 +1,25 @@ +from snuba.configs.configuration import Configuration +from snuba.web.rpc.storage_routing.routing_strategies.load_based import ( + LoadBasedRoutingStrategy, +) +from snuba.web.rpc.storage_routing.routing_strategies.outcomes_based import ( + OutcomesBasedRoutingStrategy, +) +from snuba.web.rpc.storage_routing.routing_strategies.storage_routing import ( + BaseRoutingStrategy, + RoutingDecision, +) + + +class OutcomesThenLoadBasedRoutingStrategy(BaseRoutingStrategy): + """ + Chains outcomes-based routing followed by load-based adjustments. + """ + + def _additional_config_definitions(self) -> list[Configuration]: + # No additional configs; children read their own configs by class name. + return [] + + def _update_routing_decision(self, routing_decision: RoutingDecision) -> None: + OutcomesBasedRoutingStrategy()._update_routing_decision(routing_decision) + LoadBasedRoutingStrategy()._update_routing_decision(routing_decision) From 08c12bf76afabc595e64659f143494de3b5c9407 Mon Sep 17 00:00:00 2001 From: Rachel Chen Date: Wed, 3 Dec 2025 11:51:38 -0800 Subject: [PATCH 06/11] 10 --- snuba/web/rpc/storage_routing/routing_strategies/load_based.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snuba/web/rpc/storage_routing/routing_strategies/load_based.py b/snuba/web/rpc/storage_routing/routing_strategies/load_based.py index 153e815253b..4a596fab65b 100644 --- a/snuba/web/rpc/storage_routing/routing_strategies/load_based.py +++ b/snuba/web/rpc/storage_routing/routing_strategies/load_based.py @@ -18,7 +18,7 @@ def _additional_config_definitions(self) -> list[Configuration]: name="pass_through_load_percentage", description="If cluster load is below this percentage, allow the query to run regardless of allocation policies", value_type=int, - default=20, + default=10, ), Configuration( name="pass_through_max_threads", From 5d73f81fd7ff50050958ac0db40989edb6ed2147 Mon Sep 17 00:00:00 2001 From: Rachel Chen Date: Wed, 3 Dec 2025 15:42:50 -0800 Subject: [PATCH 07/11] better --- .../routing_strategies/outcomes_load_based.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/snuba/web/rpc/storage_routing/routing_strategies/outcomes_load_based.py b/snuba/web/rpc/storage_routing/routing_strategies/outcomes_load_based.py index d2fe29f89eb..acd5e199cf5 100644 --- a/snuba/web/rpc/storage_routing/routing_strategies/outcomes_load_based.py +++ b/snuba/web/rpc/storage_routing/routing_strategies/outcomes_load_based.py @@ -16,10 +16,16 @@ class OutcomesThenLoadBasedRoutingStrategy(BaseRoutingStrategy): Chains outcomes-based routing followed by load-based adjustments. """ + def __init__(self) -> None: + self._outcomes_based_routing_strategy = OutcomesBasedRoutingStrategy() + self._load_based_routing_strategy = LoadBasedRoutingStrategy() + def _additional_config_definitions(self) -> list[Configuration]: - # No additional configs; children read their own configs by class name. - return [] + return ( + self._outcomes_based_routing_strategy.additional_config_definitions() + + self._load_based_routing_strategy.additional_config_definitions() + ) def _update_routing_decision(self, routing_decision: RoutingDecision) -> None: - OutcomesBasedRoutingStrategy()._update_routing_decision(routing_decision) - LoadBasedRoutingStrategy()._update_routing_decision(routing_decision) + self._outcomes_based_routing_strategy._update_routing_decision(routing_decision) + self._load_based_routing_strategy._update_routing_decision(routing_decision) From b5eb5e40bf781cc3130217b15d02a3536c26db43 Mon Sep 17 00:00:00 2001 From: Rachel Chen Date: Wed, 3 Dec 2025 16:23:55 -0800 Subject: [PATCH 08/11] load info --- snuba/web/rpc/storage_routing/routing_strategies/load_based.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snuba/web/rpc/storage_routing/routing_strategies/load_based.py b/snuba/web/rpc/storage_routing/routing_strategies/load_based.py index 4a596fab65b..464e7dd1ff9 100644 --- a/snuba/web/rpc/storage_routing/routing_strategies/load_based.py +++ b/snuba/web/rpc/storage_routing/routing_strategies/load_based.py @@ -33,7 +33,7 @@ def _update_routing_decision( routing_decision: RoutingDecision, ) -> None: load_info = routing_decision.routing_context.cluster_load_info - if load_info is None: + if load_info is None or load_info.cluster_load < 0: return pass_through_threshold = int(self.get_config_value("pass_through_load_percentage")) From b10ab5354416ce9ef5eb57f3f85d1a079795e167 Mon Sep 17 00:00:00 2001 From: Rachel Chen Date: Wed, 3 Dec 2025 16:53:16 -0800 Subject: [PATCH 09/11] switch --- .../storage_routing/routing_strategies/outcomes_load_based.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snuba/web/rpc/storage_routing/routing_strategies/outcomes_load_based.py b/snuba/web/rpc/storage_routing/routing_strategies/outcomes_load_based.py index acd5e199cf5..c0049345bed 100644 --- a/snuba/web/rpc/storage_routing/routing_strategies/outcomes_load_based.py +++ b/snuba/web/rpc/storage_routing/routing_strategies/outcomes_load_based.py @@ -27,5 +27,5 @@ def _additional_config_definitions(self) -> list[Configuration]: ) def _update_routing_decision(self, routing_decision: RoutingDecision) -> None: - self._outcomes_based_routing_strategy._update_routing_decision(routing_decision) self._load_based_routing_strategy._update_routing_decision(routing_decision) + self._outcomes_based_routing_strategy._update_routing_decision(routing_decision) From 4880d33c000d450cb21015bdd0c89e3dcaa5bba4 Mon Sep 17 00:00:00 2001 From: Rachel Chen Date: Thu, 4 Dec 2025 11:36:12 -0800 Subject: [PATCH 10/11] fix name and comment --- .../rpc/storage_routing/routing_strategies/load_based.py | 2 +- .../{outcomes_load_based.py => load_based_outcomes.py} | 2 +- tests/web/rpc/v1/routing_strategies/test_load_based.py | 7 +++---- 3 files changed, 5 insertions(+), 6 deletions(-) rename snuba/web/rpc/storage_routing/routing_strategies/{outcomes_load_based.py => load_based_outcomes.py} (94%) diff --git a/snuba/web/rpc/storage_routing/routing_strategies/load_based.py b/snuba/web/rpc/storage_routing/routing_strategies/load_based.py index 464e7dd1ff9..c93f4872ce0 100644 --- a/snuba/web/rpc/storage_routing/routing_strategies/load_based.py +++ b/snuba/web/rpc/storage_routing/routing_strategies/load_based.py @@ -9,7 +9,7 @@ class LoadBasedRoutingStrategy(BaseRoutingStrategy): """ - If cluster load is under a threshold, ignore recommendations and allow the query to pass through with the tier decided based on outcomes-based routing. + If cluster load is under a threshold, ignore recommendations and allow the query to pass through. This routing strategy does not decide tiering """ def _additional_config_definitions(self) -> list[Configuration]: diff --git a/snuba/web/rpc/storage_routing/routing_strategies/outcomes_load_based.py b/snuba/web/rpc/storage_routing/routing_strategies/load_based_outcomes.py similarity index 94% rename from snuba/web/rpc/storage_routing/routing_strategies/outcomes_load_based.py rename to snuba/web/rpc/storage_routing/routing_strategies/load_based_outcomes.py index c0049345bed..9f1011c8d43 100644 --- a/snuba/web/rpc/storage_routing/routing_strategies/outcomes_load_based.py +++ b/snuba/web/rpc/storage_routing/routing_strategies/load_based_outcomes.py @@ -11,7 +11,7 @@ ) -class OutcomesThenLoadBasedRoutingStrategy(BaseRoutingStrategy): +class LoadBasedOutcomesRoutingStrategy(BaseRoutingStrategy): """ Chains outcomes-based routing followed by load-based adjustments. """ diff --git a/tests/web/rpc/v1/routing_strategies/test_load_based.py b/tests/web/rpc/v1/routing_strategies/test_load_based.py index 80bcd6a5c96..c1db0993a20 100644 --- a/tests/web/rpc/v1/routing_strategies/test_load_based.py +++ b/tests/web/rpc/v1/routing_strategies/test_load_based.py @@ -20,8 +20,8 @@ ) from snuba.utils.metrics.timer import Timer from snuba.web.rpc.storage_routing.load_retriever import LoadInfo -from snuba.web.rpc.storage_routing.routing_strategies.load_based import ( - LoadBasedRoutingStrategy, +from snuba.web.rpc.storage_routing.routing_strategies.load_based_outcomes import ( + LoadBasedOutcomesRoutingStrategy, ) from snuba.web.rpc.storage_routing.routing_strategies.storage_routing import ( BaseRoutingStrategy, @@ -53,7 +53,6 @@ def _get_request_meta(hour_interval: int = 1) -> RequestMeta: @pytest.mark.clickhouse_db @pytest.mark.redis_db def test_load_based_routing_pass_through_even_if_policies_reject() -> None: - # policy that always rejects (can_run=False) class RejectAllPolicy(AllocationPolicy): def _additional_config_definitions(self) -> list[Configuration]: return [] @@ -81,7 +80,7 @@ def _update_quota_balance( ) -> None: return - strategy = LoadBasedRoutingStrategy() + strategy = LoadBasedOutcomesRoutingStrategy() request = TraceItemTableRequest(meta=_get_request_meta(hour_interval=1)) context = RoutingContext( in_msg=request, From f48f85b762b0f41ef0c3d3c21dc5b8575b4230ef Mon Sep 17 00:00:00 2001 From: Rachel Chen Date: Thu, 4 Dec 2025 11:44:29 -0800 Subject: [PATCH 11/11] c --- .../storage_routing/routing_strategies/load_based_outcomes.py | 1 + 1 file changed, 1 insertion(+) diff --git a/snuba/web/rpc/storage_routing/routing_strategies/load_based_outcomes.py b/snuba/web/rpc/storage_routing/routing_strategies/load_based_outcomes.py index 9f1011c8d43..f04bcc62a43 100644 --- a/snuba/web/rpc/storage_routing/routing_strategies/load_based_outcomes.py +++ b/snuba/web/rpc/storage_routing/routing_strategies/load_based_outcomes.py @@ -17,6 +17,7 @@ class LoadBasedOutcomesRoutingStrategy(BaseRoutingStrategy): """ def __init__(self) -> None: + super().__init__() self._outcomes_based_routing_strategy = OutcomesBasedRoutingStrategy() self._load_based_routing_strategy = LoadBasedRoutingStrategy()