Skip to content

perf: select one message per run in SQL with DISTINCT ON#38578

Open
weijun-xia wants to merge 1 commit into
langgenius:mainfrom
weijun-xia:perf/workflow-run-list-message-sql-dedup
Open

perf: select one message per run in SQL with DISTINCT ON#38578
weijun-xia wants to merge 1 commit into
langgenius:mainfrom
weijun-xia:perf/workflow-run-list-message-sql-dedup

Conversation

@weijun-xia

Copy link
Copy Markdown
Contributor

Summary

Follow-up to #38359, addressing the reviewer suggestion:

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.

Before

get_paginate_advanced_chat_workflow_runs loaded every Message matching the page's run ids (workflow_run_id IN (...)) and de-duplicated to one per run in Python via setdefault, so:

  • extra Message rows were fetched when a run had more than one message, and
  • the kept row depended on the database row return order.

After

Select a single message per run directly in SQL:

SELECT DISTINCT ON (workflow_run_id) ...
WHERE app_id = :app_id AND workflow_run_id IN (:run_ids)
ORDER BY workflow_run_id, created_at DESC, id DESC
  • One row per run, so no extra Message rows are loaded.
  • The aggregation rule is explicit and stable: the most recent message per run (created_at desc, id desc as a tie-break), rather than order-dependent.

Message.id is a UUID, so "lowest-id" is not time-ordered; created_at is used instead (there is a composite (created_at, id) index that supports this ordering).

Notes

Testing

  • Added test_get_paginate_advanced_chat_workflow_runs_dedups_one_message_per_run_in_sql: compiles the query for the PostgreSQL dialect and asserts it uses DISTINCT ON with the explicit created_at DESC ordering (de-duplication happens in SQL, not Python).
  • Existing workflow-run-service tests remain valid; ruff check and ruff format are clean.

Follow-up to langgenius#38359. The advanced-chat workflow run list loaded every
Message matching the page's run ids and then de-duplicated to one per run
in Python via setdefault, whose kept row depended on the database's row
return order.

Select a single message per run directly in SQL using DISTINCT ON
(workflow_run_id), ordered by created_at desc then id desc, so extra
Message rows are not fetched when a run has more than one message, and the
aggregation rule (most recent message per run) is explicit and stable
instead of order-dependent. Adds a test asserting the de-duplication is
pushed into SQL.
@dosubot dosubot Bot added the size:S This PR changes 10-29 lines, ignoring generated files. label Jul 9, 2026
@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Pyrefly Type Coverage

Metric Base PR Delta
Type coverage 52.59% 52.59% +0.00%
Strict coverage 52.11% 52.11% +0.00%
Typed symbols 32,538 32,541 +3
Untyped symbols 29,605 29,605 0
Modules 2982 2982 0

@weijun-xia

Copy link
Copy Markdown
Contributor Author

@wylswz following up on your review suggestion from #38359.

Before finalizing, I'd like to confirm one design decision with you: this PR makes the "one message per run" selection explicit as the latest message per run:

SELECT DISTINCT ON (workflow_run_id) ...
WHERE app_id = :app_id AND workflow_run_id IN (:run_ids)
ORDER BY workflow_run_id, created_at DESC, id DESC

I went with created_at DESC rather than the "lowest-id" option you mentioned, because Message.id is a UUID (not sequential / not time-ordered), so "lowest id" wouldn't actually map to the first/earliest message. Using created_at (with id as a stable tie-break) gives a well-defined "most recent message per run", and it matches how workflow_event_snapshot_service already resolves a run's message (order_by(desc(created_at))).

Could you confirm that "latest message per run" is the aggregation rule you'd want here? If you'd rather use "earliest message" (created_at ASC) or some other rule, I'm happy to switch — it's a one-line change in the ORDER BY.

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

Labels

size:S This PR changes 10-29 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant