Skip to content

Load a send identity's candidates once per execution (#1014) - #1125

Merged
rmcdaniel merged 4 commits into
mainfrom
perf/1014-send-candidate-cache
Nov 13, 2026
Merged

rmcdaniel merged 4 commits into
mainfrom
perf/1014-send-candidate-cache

Conversation

@rmcdaniel

Copy link
Copy Markdown
Member

Closes #1014.

Problem: MessageStream::recordedSend listed every exactly matched outbound row for the send identity on each send, then took the offset-th. A workflow that repeats one send N times therefore read 1+2+…+N rows per replay, and ran N candidate queries.

Change: MessageReplayLedger keeps the matched list per send identity for the execution. It loads once through the same narrowing query and exact field comparison, and rememberSend() extends it with the rows the execution writes. The send takes its offset from that list. There's no durable schema change and no matching or ordering change. The ledger is per execution, as before, so nothing is reused across attempts or runs. recordedReceive is unchanged: live consumption at a step can add rows during the execution, so caching it would need its own invalidation, and the issue's measured cost is the send path.

Tests:

  • V2MessageStreamReplayTest::testReplayingIdenticalSendsLoadsTheirCandidatesOncePerExecution: one candidate query for both 2 and 8 identical sends (base: 2 and 8).
  • Corpus fixture message-stream-identical-sends-load-once with consumer testMessageStreamIdenticalSendsCorpusLoadsCandidatesOncePerReplay: six sends give 1 load (base: 6). The cold-replay runner passes the fixture.
  • All message-stream suites pass locally (V2MessageStreamReplayTest 20, MessageServiceTest, MessageStreamTest, MessageStreamCursorTest, V2MessageCursorContinueAsNewTest, V2MessageStreamAuthoringTest).

🤖 Generated with Claude Code

rmcdaniel and others added 2 commits November 13, 2026 02:14
MessageStream::recordedSend listed every exactly matched outbound row for the identity on each send, so a workflow repeating one send N times did 1+2+...+N row reads per replay. MessageReplayLedger now keeps the matched list per identity for the execution (loaded once, extended with rows the execution writes via rememberSend) and the send takes its offset from it. No durable schema or matching change; the ledger is per execution as before.

V2MessageStreamReplayTest::testReplayingIdenticalSendsLoadsTheirCandidatesOncePerExecution: one candidate query for 2 and for 8 identical sends (base: 2 and 8).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
message-stream-identical-sends-load-once: six identical sends in one turn; a query replay loads that identity's candidates once (base: six times).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@rmcdaniel

Copy link
Copy Markdown
Member Author

Security PASS at 272dc02, on green CI.

  • Same query, same match. The loader is the base query moved into a closure. The instance scope, the redrive-chain source_workflow_run_id IN runIds(), outbound direction, the exact per-field comparison after the collation-narrowing query, and the ordering are all unchanged.
  • Cache key and lifetime. The identity is the JSON of every matched field: channel, stream key, target, payload reference, correlation id and idempotency key. So one identity's list can't answer a different send. The ledger is created per Workflow object, which is one execution of one run, so nothing carries across attempts, runs or tenants.
  • Appends. rememberSend only appends rows this execution wrote through sendMessage with those same fields, and only once the identity's list is loaded. Read-only replay still returns its unsaved description and caches nothing. receive is untouched.

Non-blocking: the cached rows are model instances from the first load. If a caller ever reads mutable columns from the returned row, such as consume_state, within the same execution, they may be staler than with base. Not a security issue, just worth knowing.

@sonarqubecloud

Copy link
Copy Markdown

@rmcdaniel
rmcdaniel merged commit a7a836f into main Nov 13, 2026
30 checks passed
@rmcdaniel
rmcdaniel deleted the perf/1014-send-candidate-cache branch November 13, 2026 02:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Cache workflow-code send candidates per identity during an execution

1 participant