perf: batch-load messages in advanced-chat workflow run list to remove N+1#38359
Conversation
…e N+1 WorkflowRunService.get_paginate_advanced_chat_workflow_runs attached message_id / conversation_id to each run by reading the deprecated WorkflowRun.message property inside the loop, which issues one select(Message) query per run - an N+1 that scales with page size on the console "workflow runs" log list. Batch-load the messages for the whole page in a single query (Message.app_id == app_model.id, Message.workflow_run_id.in_(run_ids)) and look them up in memory, preserving the exact filter semantics. Query count drops from N+1 to a constant regardless of page size. Mirrors the same optimization already merged for TokenBufferMemory (langgenius#38002).
Pyrefly Diffbase → PR--- /tmp/pyrefly_base.txt 2026-07-08 10:12:01.493940018 +0000
+++ /tmp/pyrefly_pr.txt 2026-07-08 10:11:46.300894587 +0000
@@ -8127,9 +8127,9 @@
ERROR Argument `list[int | str]` is not assignable to parameter `mentioned_user_ids` with type `Sequence[str]` in function `services.workflow_comment_service.WorkflowCommentService._filter_valid_mentioned_user_ids` [bad-argument-type]
--> tests/unit_tests/services/test_workflow_comment_service.py:49:13
ERROR Argument `dict[str, str]` is not assignable to parameter `args` with type `WorkflowRunListArgs` in function `services.workflow_run_service.WorkflowRunService.get_paginate_workflow_runs` [bad-argument-type]
- --> tests/unit_tests/services/test_workflow_run_service.py:102:18
+ --> tests/unit_tests/services/test_workflow_run_service.py:109:18
ERROR `Literal['2']` is not assignable to TypedDict key `limit` with type `int` [bad-typed-dict-key]
- --> tests/unit_tests/services/test_workflow_run_service.py:132:103
+ --> tests/unit_tests/services/test_workflow_run_service.py:139:103
ERROR Argument `dict[str, dict[str, str | dict[str, str]] | str]` is not assignable to parameter `object` with type `dict[str, dict[str, list[Unknown] | str] | str]` in function `list.append` [bad-argument-type]
--> tests/unit_tests/services/test_workflow_service.py:225:13
ERROR `dict[@_, @_]` is not assignable to TypedDict key `data` with type `BaseNodeData` [bad-typed-dict-key]
|
Pyrefly Type Coverage
|
There was a problem hiding this comment.
Overall LGTM.
One possible follow-up: can we push the “one message per run” selection into SQL as well, so we don’t load extra Message rows when multiple rows share the same workflow_run_id? If we do that, we should first make the aggregation rule explicit (for example latest or lowest-id message), since the current Python setdefault preserves whichever row comes back first.
|
@wylswz thanks for the review and the follow-up suggestion! I opened #38578 to implement it. It pushes the "one message per run" selection into SQL via One note: since |
Summary
WorkflowRunService.get_paginate_advanced_chat_workflow_runs(the console "workflow runs" log list for advanced-chat apps) attachesmessage_id/conversation_idto each run by reading the deprecatedWorkflowRun.messageproperty inside the loop. That property runsselect(Message).where(Message.app_id == self.app_id, Message.workflow_run_id == self.id)- i.e. one query per run, an N+1 that scales with page size.Change
Batch-load the messages for the whole page in a single query and look them up in memory:
app_id+workflow_run_id); all runs on a page belong toapp_model, soMessage.app_id == app_model.idmatchesrun.app_id.setdefaultkeeps one message per run, mirroringscalar()'s single-row behavior.WorkflowRun.messageproperty is left untouched for other callers.Same class as the already-merged #38002 (batch-load
MessageFileinTokenBufferMemory).Tests
Updated
test_get_paginate_advanced_chat_workflow_runs_should_attach_message_fields_when_message_existsfor the batched path, and addedtest_get_paginate_advanced_chat_workflow_runs_batch_loads_messages_without_n_plus_one, which assertsdb.session.scalarsis called exactly once regardless of run count (regression guard against N+1).