From ae426bf6fe4eb11431f1008e7065ec6aa0383a33 Mon Sep 17 00:00:00 2001 From: Renat Akhmerov Date: Mon, 14 May 2018 17:09:29 +0700 Subject: [PATCH] Use "passive_deletes=True" in ORM relationships * Using "passive_deletes=True" when configuring relationships with SQLAlchemy significantly reduces memory footprint when deleting a graph of objects (e.g. root workflow execution with all dependent tasks, actions and sub workflows) from DB. This happens because in this case SQLAlchemy doesn't load those dependent objects that are not yet in the session and rather relies on cascading mechanisms of databases which are activated by the proper configuration of foreign key columns. For example, "ON DELETE CASCADE" clause in the foreign key definition in case of for MySQL. Particularly, this change reduces memory footprint of API processes when deleting large graphs of executions and execution expiration policy mechanism where the same happens but on the engine side. * Added a test to make sure that cascaded deletion works well with this new property. Related-Bug: #1757966 Change-Id: I36988817fb8e7695094ef98958b8aa151208fadb (cherry picked from commit afb0f584c5f1218ec73b2b6ecd9d7509c1df4594) --- mistral/cmd/launch.py | 1 + mistral/db/v2/sqlalchemy/models.py | 9 ++-- .../tests/unit/engine/test_subworkflows.py | 42 +++++++++++++++++++ 3 files changed, 49 insertions(+), 3 deletions(-) diff --git a/mistral/cmd/launch.py b/mistral/cmd/launch.py index 88926a8dc..2922d01be 100644 --- a/mistral/cmd/launch.py +++ b/mistral/cmd/launch.py @@ -95,6 +95,7 @@ def launch_event_engine(): def launch_api(): server = api_service.WSGIService('mistral_api') + launch_process(server, workers=server.workers) diff --git a/mistral/db/v2/sqlalchemy/models.py b/mistral/db/v2/sqlalchemy/models.py index 3737520df..aa3c1b0d4 100644 --- a/mistral/db/v2/sqlalchemy/models.py +++ b/mistral/db/v2/sqlalchemy/models.py @@ -288,7 +288,8 @@ TaskExecution.action_executions = relationship( backref=backref('task_execution', remote_side=[TaskExecution.id]), cascade='all, delete-orphan', foreign_keys=ActionExecution.task_execution_id, - lazy='select' + lazy='select', + passive_deletes=True ) sa.Index( @@ -309,7 +310,8 @@ TaskExecution.workflow_executions = relationship( backref=backref('task_execution', remote_side=[TaskExecution.id]), cascade='all, delete-orphan', foreign_keys=WorkflowExecution.task_execution_id, - lazy='select' + lazy='select', + passive_deletes=True ) sa.Index( @@ -337,7 +339,8 @@ WorkflowExecution.task_executions = relationship( backref=backref('workflow_execution', remote_side=[WorkflowExecution.id]), cascade='all, delete-orphan', foreign_keys=TaskExecution.workflow_execution_id, - lazy='select' + lazy='select', + passive_deletes=True ) sa.Index( diff --git a/mistral/tests/unit/engine/test_subworkflows.py b/mistral/tests/unit/engine/test_subworkflows.py index b9b11f5c5..bcb01b579 100644 --- a/mistral/tests/unit/engine/test_subworkflows.py +++ b/mistral/tests/unit/engine/test_subworkflows.py @@ -21,6 +21,7 @@ from mistral import context as auth_context from mistral.db.v2 import api as db_api from mistral import exceptions as exc from mistral.services import workbooks as wb_service +from mistral.services import workflows as wf_service from mistral.tests.unit.engine import base from mistral.workflow import states @@ -457,3 +458,44 @@ class SubworkflowsTest(base.EngineTestCase): self.assertIsNone(wf1_ex.root_execution_id, None) self.assertEqual(wf2_ex.root_execution_id, wf1_ex.id) self.assertEqual(wf3_ex.root_execution_id, wf1_ex.id) + + def test_cascade_delete(self): + wf_text = """ + version: 2.0 + + wf: + tasks: + task1: + workflow: sub_wf1 + + task2: + workflow: sub_wf2 + + sub_wf1: + tasks: + task1: + action: std.noop + + sub_wf2: + tasks: + task1: + action: std.noop + """ + + wf_service.create_workflows(wf_text) + + wf_ex = self.engine.start_workflow('wf') + + self.await_workflow_success(wf_ex.id) + + self.assertEqual(3, len(db_api.get_workflow_executions())) + self.assertEqual(4, len(db_api.get_task_executions())) + self.assertEqual(2, len(db_api.get_action_executions())) + + # Now delete the root workflow execution and make sure that + # all dependent objects are deleted as well. + db_api.delete_workflow_execution(wf_ex.id) + + self.assertEqual(0, len(db_api.get_workflow_executions())) + self.assertEqual(0, len(db_api.get_task_executions())) + self.assertEqual(0, len(db_api.get_action_executions()))