-
-
Notifications
You must be signed in to change notification settings - Fork 4.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(workflow_engine): Split fast and slow condition evaluation #84275
Changes from all commits
06c5ce8
cbb8005
3aa2e93
20607fe
212803d
4299ce1
a18630f
23d9e0b
d70701f
3f79028
5561b44
df05a43
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
from sentry.workflow_engine.models.data_condition import DataCondition, is_slow_condition | ||
|
||
|
||
def split_conditions_by_speed( | ||
conditions: list[DataCondition], | ||
) -> tuple[list[DataCondition], list[DataCondition]]: | ||
fast_conditions: list[DataCondition] = [] | ||
slow_conditions: list[DataCondition] = [] | ||
|
||
for condition in conditions: | ||
if is_slow_condition(condition): | ||
slow_conditions.append(condition) | ||
else: | ||
fast_conditions.append(condition) | ||
|
||
return fast_conditions, slow_conditions |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -4,10 +4,17 @@ | |||||||||||||||||||||||||||||||||||||||||||
import sentry_sdk | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
from sentry import buffer | ||||||||||||||||||||||||||||||||||||||||||||
from sentry.db.models.manager.base_query_set import BaseQuerySet | ||||||||||||||||||||||||||||||||||||||||||||
from sentry.utils import json, metrics | ||||||||||||||||||||||||||||||||||||||||||||
from sentry.workflow_engine.models import Detector, Workflow, WorkflowDataConditionGroup | ||||||||||||||||||||||||||||||||||||||||||||
from sentry.workflow_engine.models.workflow import get_slow_conditions | ||||||||||||||||||||||||||||||||||||||||||||
from sentry.workflow_engine.processors.action import evaluate_workflow_action_filters | ||||||||||||||||||||||||||||||||||||||||||||
from sentry.workflow_engine.models import ( | ||||||||||||||||||||||||||||||||||||||||||||
Action, | ||||||||||||||||||||||||||||||||||||||||||||
DataCondition, | ||||||||||||||||||||||||||||||||||||||||||||
DataConditionGroup, | ||||||||||||||||||||||||||||||||||||||||||||
Detector, | ||||||||||||||||||||||||||||||||||||||||||||
Workflow, | ||||||||||||||||||||||||||||||||||||||||||||
WorkflowDataConditionGroup, | ||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||
from sentry.workflow_engine.processors.action import filter_recently_fired_workflow_actions | ||||||||||||||||||||||||||||||||||||||||||||
from sentry.workflow_engine.processors.data_condition_group import evaluate_condition_group | ||||||||||||||||||||||||||||||||||||||||||||
from sentry.workflow_engine.processors.detector import get_detector_by_event | ||||||||||||||||||||||||||||||||||||||||||||
from sentry.workflow_engine.types import WorkflowJob | ||||||||||||||||||||||||||||||||||||||||||||
|
@@ -17,6 +24,7 @@ | |||||||||||||||||||||||||||||||||||||||||||
WORKFLOW_ENGINE_BUFFER_LIST_KEY = "workflow_engine_delayed_processing_buffer" | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
# TODO remove this method | ||||||||||||||||||||||||||||||||||||||||||||
def get_data_condition_groups_to_fire( | ||||||||||||||||||||||||||||||||||||||||||||
workflows: set[Workflow], job: WorkflowJob | ||||||||||||||||||||||||||||||||||||||||||||
) -> dict[int, list[int]]: | ||||||||||||||||||||||||||||||||||||||||||||
|
@@ -30,7 +38,7 @@ def get_data_condition_groups_to_fire( | |||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
for workflow_dcg in workflow_dcgs: | ||||||||||||||||||||||||||||||||||||||||||||
action_condition = workflow_dcg.condition_group | ||||||||||||||||||||||||||||||||||||||||||||
evaluation, result = evaluate_condition_group(action_condition, job) | ||||||||||||||||||||||||||||||||||||||||||||
evaluation, result, _ = evaluate_condition_group(action_condition, job) | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
if evaluation: | ||||||||||||||||||||||||||||||||||||||||||||
workflow_action_groups[workflow_dcg.workflow_id].append(action_condition.id) | ||||||||||||||||||||||||||||||||||||||||||||
|
@@ -69,19 +77,54 @@ def evaluate_workflow_triggers(workflows: set[Workflow], job: WorkflowJob) -> se | |||||||||||||||||||||||||||||||||||||||||||
workflows_to_enqueue: set[Workflow] = set() | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
for workflow in workflows: | ||||||||||||||||||||||||||||||||||||||||||||
if workflow.evaluate_trigger_conditions(job): | ||||||||||||||||||||||||||||||||||||||||||||
triggered_workflows.add(workflow) | ||||||||||||||||||||||||||||||||||||||||||||
evaluation, remaining_conditions = workflow.evaluate_trigger_conditions(job) | ||||||||||||||||||||||||||||||||||||||||||||
if remaining_conditions: | ||||||||||||||||||||||||||||||||||||||||||||
workflows_to_enqueue.add(workflow) | ||||||||||||||||||||||||||||||||||||||||||||
else: | ||||||||||||||||||||||||||||||||||||||||||||
if get_slow_conditions(workflow): | ||||||||||||||||||||||||||||||||||||||||||||
# enqueue to be evaluated later | ||||||||||||||||||||||||||||||||||||||||||||
workflows_to_enqueue.add(workflow) | ||||||||||||||||||||||||||||||||||||||||||||
if evaluation: | ||||||||||||||||||||||||||||||||||||||||||||
# Only add workflows that have no remaining conditions to check | ||||||||||||||||||||||||||||||||||||||||||||
triggered_workflows.add(workflow) | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
if workflows_to_enqueue: | ||||||||||||||||||||||||||||||||||||||||||||
enqueue_workflows(workflows_to_enqueue, job) | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
return triggered_workflows | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
def evaluate_workflows_action_filters( | ||||||||||||||||||||||||||||||||||||||||||||
workflows: set[Workflow], | ||||||||||||||||||||||||||||||||||||||||||||
job: WorkflowJob, | ||||||||||||||||||||||||||||||||||||||||||||
) -> BaseQuerySet[Action]: | ||||||||||||||||||||||||||||||||||||||||||||
filtered_action_groups: set[DataConditionGroup] = set() | ||||||||||||||||||||||||||||||||||||||||||||
enqueued_conditions: list[DataCondition] = [] | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
# gets the list of the workflow ids, and then get the workflow_data_condition_groups for those workflows | ||||||||||||||||||||||||||||||||||||||||||||
workflow_ids = {workflow.id for workflow in workflows} | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
action_conditions = DataConditionGroup.objects.filter( | ||||||||||||||||||||||||||||||||||||||||||||
workflowdataconditiongroup__workflow_id__in=workflow_ids | ||||||||||||||||||||||||||||||||||||||||||||
).distinct() | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
for action_condition in action_conditions: | ||||||||||||||||||||||||||||||||||||||||||||
evaluation, result, remaining_conditions = evaluate_condition_group(action_condition, job) | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
if remaining_conditions: | ||||||||||||||||||||||||||||||||||||||||||||
# If there are remaining conditions for the action filter to evaluate, | ||||||||||||||||||||||||||||||||||||||||||||
# then return the list of conditions to enqueue | ||||||||||||||||||||||||||||||||||||||||||||
enqueued_conditions.extend(remaining_conditions) | ||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i had planned to enqueue the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. on second thought, probably need to rethink the enqueuing logic since i am planning to have the key be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah, i ended up changing the enqueue method a bit: sentry/src/sentry/workflow_engine/processors/workflow.py Lines 32 to 52 in 67d137e
Since each condition knows it's condition group, i just made the enqueue method signature a little more ergonomic There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah this works. will have to refactor my PR using this 😅 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. out of curiosity, why's that? aren't you just reading from the buffer and that's all the format we chatted about this mornin' There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. oh jk i need to learn to read There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i think i was misled that we are passing the conditions be enqueued when we're still actually enqueuing the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. eh, probably don't need them all, we could probably move that list coercion up and out of this method and just take a list of ids in the method signature, but can figure that out on the other PR :) |
||||||||||||||||||||||||||||||||||||||||||||
else: | ||||||||||||||||||||||||||||||||||||||||||||
# if we don't have any other conditions to evaluate, add the action to the list | ||||||||||||||||||||||||||||||||||||||||||||
if evaluation: | ||||||||||||||||||||||||||||||||||||||||||||
filtered_action_groups.add(action_condition) | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
# get the actions for any of the triggered data condition groups | ||||||||||||||||||||||||||||||||||||||||||||
actions = Action.objects.filter( | ||||||||||||||||||||||||||||||||||||||||||||
dataconditiongroupaction__condition_group__in=filtered_action_groups | ||||||||||||||||||||||||||||||||||||||||||||
).distinct() | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
return filter_recently_fired_workflow_actions(actions, job["event"].group) | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
def process_workflows(job: WorkflowJob) -> set[Workflow]: | ||||||||||||||||||||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||||||||||||||||||
This method will get the detector based on the event, and then gather the associated workflows. | ||||||||||||||||||||||||||||||||||||||||||||
|
@@ -101,7 +144,7 @@ def process_workflows(job: WorkflowJob) -> set[Workflow]: | |||||||||||||||||||||||||||||||||||||||||||
# Get the workflows, evaluate the when_condition_group, finally evaluate the actions for workflows that are triggered | ||||||||||||||||||||||||||||||||||||||||||||
workflows = set(Workflow.objects.filter(detectorworkflow__detector_id=detector.id).distinct()) | ||||||||||||||||||||||||||||||||||||||||||||
triggered_workflows = evaluate_workflow_triggers(workflows, job) | ||||||||||||||||||||||||||||||||||||||||||||
actions = evaluate_workflow_action_filters(triggered_workflows, job) | ||||||||||||||||||||||||||||||||||||||||||||
actions = evaluate_workflows_action_filters(triggered_workflows, job) | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
with sentry_sdk.start_span(op="workflow_engine.process_workflows.trigger_actions"): | ||||||||||||||||||||||||||||||||||||||||||||
for action in actions: | ||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i think we should enqueue the same thing in the buffer as in
evaluate_workflow_action
filters, but i assume this is happening in a follow up PRThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep! rather than us trying to create a data structure here and then enqueue a bunch at the end, i ended up just enqueuing directly in the other PR. that made it so we don't need to have an additional query to match the workflows to the data condition groups when enqueuing them.