diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 5719ee2d16..6fee54c7a4 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -24,6 +24,9 @@ Fixed * Fix ``TypeError`` when displaying help for actions whose parameters have no ``description`` key. #6375 * Fix utf-8 encode before checking paramter max size #6352 * Fix stuck running workflow tasks #6398 (by @guzzijones12@gmail.com) +* Improve workflow cancellation performance for tasks with a large with-items fan-out by + batch-fetching child action executions and using a server-side count in ``is_children_active`` + instead of loading every child execution document into memory. (by @guzzijones12@gmail.com) Changed ~~~~~~~ diff --git a/contrib/runners/orquesta_runner/orquesta_runner/orquesta_runner.py b/contrib/runners/orquesta_runner/orquesta_runner/orquesta_runner.py index 717ec979c4..9903938585 100644 --- a/contrib/runners/orquesta_runner/orquesta_runner/orquesta_runner.py +++ b/contrib/runners/orquesta_runner/orquesta_runner/orquesta_runner.py @@ -276,13 +276,21 @@ def cancel(self): } # Request cancellation of tasks that are workflows and still running. - for child_ex_id in self.execution.children: - child_ex = ex_db_access.ActionExecution.get(id=child_ex_id) - if self.task_cancelable(child_ex): - ac_svc.request_cancellation( - lv_db_access.LiveAction.get(id=child_ex.liveaction_id), - self.context.get("user", None), - ) + # Batch-fetch the child action executions in a single query instead of + # issuing one get() per child. For a task with a large with-items + # fan-out the per-child gets add up to thousands of serial DB + # round-trips during cancellation. + child_ex_ids = self.execution.children + + if child_ex_ids: + child_exs = ex_db_access.ActionExecution.query(id__in=child_ex_ids) + + for child_ex in child_exs: + if self.task_cancelable(child_ex): + ac_svc.request_cancellation( + lv_db_access.LiveAction.get(id=child_ex.liveaction_id), + self.context.get("user", None), + ) status = ( ac_const.LIVEACTION_STATUS_CANCELING diff --git a/lockfiles/st2.lock b/lockfiles/st2.lock index 546f12698e..7a9f94672c 100644 --- a/lockfiles/st2.lock +++ b/lockfiles/st2.lock @@ -3085,7 +3085,7 @@ "artifacts": [ { "algorithm": "sha256", - "hash": "491767e81c1bb11a54fb68d1a24119bdeede593a2beccca5bc09bfed36fdb35c", + "hash": "b9feb1769b48102061fe4fc59b2f5ad600bc2ac0b55cf12ef5fe49464ac0d230", "url": "git+https://github.com/StackStorm/orquesta.git" } ], diff --git a/st2common/st2common/services/action.py b/st2common/st2common/services/action.py index d5f35eb204..3383ba5f2d 100644 --- a/st2common/st2common/services/action.py +++ b/st2common/st2common/services/action.py @@ -546,19 +546,21 @@ def is_children_active(liveaction_id): if execution_db.runner["name"] not in action_constants.WORKFLOW_RUNNER_TYPES: return False - children_execution_dbs = ActionExecution.query(parent=str(execution_db.id)) - inactive_statuses = action_constants.LIVEACTION_COMPLETED_STATES + [ action_constants.LIVEACTION_STATUS_PAUSED, action_constants.LIVEACTION_STATUS_PENDING, ] - completed = [ - child_exec_db.status in inactive_statuses - for child_exec_db in children_execution_dbs - ] + # Count children whose status is not inactive with a server-side query + # instead of loading every child execution document into memory. This + # matters for tasks with a large with-items fan-out, where the parent can + # have thousands of children. + active_children_count = ActionExecution.count( + parent=str(execution_db.id), + status__nin=inactive_statuses, + ) - return not all(completed) + return active_children_count > 0 def _cleanup_liveaction(liveaction):