Skip to content

Conversation

@xurui-c
Copy link
Member

@xurui-c xurui-c commented Nov 9, 2025

Basically does what OutcomesBasedRoutingStrategy does except when cluster load is low enough, we let the query through even if allocation policies say no

Rollout plan:

  1. only deploy this strategy for org 1 (Sentry) via Snuba Admin by setting storage_routing_config_override = {'{"1": {"version": 1, "config": {"LoadBasedOutcomesRoutingStrategy": 1.0}}}',
  2. deploy to 50% of our customers via setting default_storage_routing_config = '{"version": 1, "config": {"LoadBasedOutcomesRoutingStrategy": 0.5, "OutcomesBasedRoutingStrategy": 0.5}}'

merging this PR does not automatically make any queries go through this routing strategy, I have to set the routing config in Snuba Admin

@xurui-c xurui-c marked this pull request as ready for review November 10, 2025 17:37
@xurui-c xurui-c requested review from a team as code owners November 10, 2025 17:37
Comment on lines 12 to 15
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.
"""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this inheriting from OutcomesBasedRoutingStrategy? shouldn't it inherit BaseRoutingStrategy?

I think it's mixing concerns weirdly to make the load-based routing in any way aware or coupled to outcomes-based routing. Some third entity/module should chain the two together if that's necessary.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Originally the intention was to tightly couple load-based routing strategy with outcomes-based, but now thinking about it, it makes sense to separate the two, especially if in the future we want to use a different strategy to decide which tier. I used a new strategy to chain the two together

Rachel Chen added 2 commits December 2, 2025 13:55
Comment on lines 23 to 25
def _update_routing_decision(self, routing_decision: RoutingDecision) -> None:
OutcomesBasedRoutingStrategy()._update_routing_decision(routing_decision)
LoadBasedRoutingStrategy()._update_routing_decision(routing_decision)

This comment was marked as outdated.

Comment on lines 36 to 42
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:

This comment was marked as outdated.

Comment on lines 19 to 21
def __init__(self) -> None:
self._outcomes_based_routing_strategy = OutcomesBasedRoutingStrategy()
self._load_based_routing_strategy = LoadBasedRoutingStrategy()

This comment was marked as outdated.

routing_decision: RoutingDecision,
) -> None:
load_info = routing_decision.routing_context.cluster_load_info
if load_info is None or load_info.cluster_load < 0:
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this means something in load retriever went wrong, in which case this would be as if only outcomes based routing strategy ran, which effectively means everything works the same as before

Comment on lines 19 to 21
def __init__(self) -> None:
self._outcomes_based_routing_strategy = OutcomesBasedRoutingStrategy()
self._load_based_routing_strategy = LoadBasedRoutingStrategy()

This comment was marked as outdated.

@codecov
Copy link

codecov bot commented Dec 4, 2025

❌ 1 Tests Failed:

Tests completed Failed Passed Skipped
2715 1 2714 12
View the top 1 failed test(s) by shortest run time
tests.web.rpc.v1.routing_strategies.test_load_based::test_load_based_routing_pass_through_even_if_policies_reject
Stack Traces | 0.334s run time
Traceback (most recent call last):
  File "/.venv/lib/python3.11........./site-packages/_pytest/runner.py", line 341, in from_call
    result: TResult | None = func()
                             ^^^^^^
  File "/.venv/lib/python3.11........./site-packages/_pytest/runner.py", line 242, in <lambda>
    lambda: runtest_hook(item=item, **kwds), when=when, reraise=reraise
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/.venv/lib/python3.11....../site-packages/pluggy/_hooks.py", line 513, in __call__
    return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/.venv/lib/python3.11....../site-packages/pluggy/_manager.py", line 120, in _hookexec
    return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/.venv/lib/python3.11.........................../site-packages/pluggy/_callers.py", line 182, in _multicall
    return outcome.get_result()
           ^^^^^^^^^^^^^^^^^^^^
  File "/.venv/lib/python3.11.../site-packages/pluggy/_result.py", line 100, in get_result
    raise exc.with_traceback(exc.__traceback__)
  File "/.venv/lib/python3.11.........................../site-packages/pluggy/_callers.py", line 167, in _multicall
    teardown.throw(outcome._exception)
  File "/.venv/lib/python3.11....../site-packages/_pytest/threadexception.py", line 92, in pytest_runtest_call
    yield from thread_exception_runtest_hook()
  File "/.venv/lib/python3.11....../site-packages/_pytest/threadexception.py", line 68, in thread_exception_runtest_hook
    yield
  File "/.venv/lib/python3.11.........................../site-packages/pluggy/_callers.py", line 167, in _multicall
    teardown.throw(outcome._exception)
  File "/.venv/lib/python3.11....../site-packages/_pytest/unraisableexception.py", line 95, in pytest_runtest_call
    yield from unraisable_exception_runtest_hook()
  File "/.venv/lib/python3.11....../site-packages/_pytest/unraisableexception.py", line 70, in unraisable_exception_runtest_hook
    yield
  File "/.venv/lib/python3.11.........................../site-packages/pluggy/_callers.py", line 167, in _multicall
    teardown.throw(outcome._exception)
  File "/.venv/lib/python3.11....../site-packages/_pytest/logging.py", line 846, in pytest_runtest_call
    yield from self._runtest_for(item, "call")
  File "/.venv/lib/python3.11....../site-packages/_pytest/logging.py", line 829, in _runtest_for
    yield
  File "/.venv/lib/python3.11.........................../site-packages/pluggy/_callers.py", line 167, in _multicall
    teardown.throw(outcome._exception)
  File "/.venv/lib/python3.11.../site-packages/_pytest/capture.py", line 880, in pytest_runtest_call
    return (yield)
            ^^^^^
  File "/.venv/lib/python3.11.........................../site-packages/pluggy/_callers.py", line 167, in _multicall
    teardown.throw(outcome._exception)
  File "/.venv/lib/python3.11.../site-packages/_pytest/skipping.py", line 257, in pytest_runtest_call
    return (yield)
            ^^^^^
  File "/.venv/lib/python3.11.........................../site-packages/pluggy/_callers.py", line 103, in _multicall
    res = hook_impl.function(*args)
          ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/.venv/lib/python3.11........./site-packages/_pytest/runner.py", line 174, in pytest_runtest_call
    item.runtest()
  File "/.venv/lib/python3.11....../site-packages/_pytest/python.py", line 1627, in runtest
    self.ihook.pytest_pyfunc_call(pyfuncitem=self)
  File "/.venv/lib/python3.11....../site-packages/pluggy/_hooks.py", line 513, in __call__
    return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/.venv/lib/python3.11....../site-packages/pluggy/_manager.py", line 120, in _hookexec
    return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/.venv/lib/python3.11.........................../site-packages/pluggy/_callers.py", line 139, in _multicall
    raise exception.with_traceback(exception.__traceback__)
  File "/.venv/lib/python3.11.........................../site-packages/pluggy/_callers.py", line 103, in _multicall
    res = hook_impl.function(*args)
          ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/.venv/lib/python3.11....../site-packages/_pytest/python.py", line 159, in pytest_pyfunc_call
    result = testfunction(**testargs)
             ^^^^^^^^^^^^^^^^^^^^^^^^
  File ".../v1/routing_strategies/test_load_based.py", line 83, in test_load_based_routing_pass_through_even_if_policies_reject
    strategy = LoadBasedOutcomesRoutingStrategy()
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File ".../storage_routing/routing_strategies/load_based_outcomes.py", line 20, in __init__
    super().__init__()
  File ".../storage_routing/routing_strategies/storage_routing.py", line 263, in __init__
    self._get_overridden_additional_config_defaults(default_config_overrides)
  File ".../snuba/configs/configuration.py", line 352, in _get_overridden_additional_config_defaults
    definitions = self._additional_config_definitions()
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File ".../storage_routing/routing_strategies/load_based_outcomes.py", line 26, in _additional_config_definitions
    self._outcomes_based_routing_strategy.additional_config_definitions()
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'LoadBasedOutcomesRoutingStrategy' object has no attribute '_outcomes_based_routing_strategy'

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants