Repository navigation
Session.fork publishes its copy in one transaction - #9
Merged
Merged
Conversation
Against a remote Postgres each durable publish is its own transaction of eight or nine round trips, so forking a 234-message chat (936 rows) took about 30 s at a 2 ms round trip. EventV2.publishMany publishes durable events of one aggregate in a single transaction: one lock, one sequence read, the projectors per event as before, then one sequence write and chunked event inserts. Listeners are notified per event after the commit. Same rows, same events, measured interleaved against the old path at a 2 ms round trip: 47-77 s before, 8.3-8.7 s after; 6-7 s with 16 KB parts. The copy commits all or nothing, and fork removes the new session if it does not. publishMany is a copy of commitDurableEvent's non-replay path rather than a refactor of it, so upstream changes there rebase cleanly. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
This PR doesn't fully meet our contributing guidelines and PR template. What needs to be fixed:
Please edit this PR description to address the above within 72 hours, or it will be automatically closed. If you believe this was flagged incorrectly, please let a maintainer know. |
|
Hey! Your PR title Please update it to start with one of:
Where See CONTRIBUTING.md for details. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
DocuMentor's branch feature calls
Session.fork. On production, forking a 234-message chat took ~29 s (production bug report of 2026-10-19). OpenCode copies each message and each part as its own durable publish. Here that is a transaction of 8–9 statements, each a round trip to the agent-state Postgres on the VM: BEGIN, advisory lock, sequence read, event-id check, (part read), row upsert, sequence upsert, event insert, COMMIT.What changes
packages/core/src/event.ts: newEventV2.publishMany(publications, { location? })(plusPublicationandpublication) for durable events of one aggregate. Everything runs in one transaction under one advisory lock: one sequence read, the existing projectors per event in order, one sequence upsert, and event inserts in chunks of 200. After the commit there is one durable wake, then onenotifyper event in sequence order, aspublishdoes. Mixed aggregates or live-only events are refused. It is a copy ofcommitDurableEvent's non-replay path, not a refactor, so upstream edits to that function rebase cleanly.packages/opencode/src/event-v2-bridge.ts: routespublishManywith the same instance location aspublish.packages/opencode/src/session/session.ts:forkbuilds the sameMessageUpdated/PartUpdateddata as before and publishes it withpublishMany. The copy is all or nothing, and if it fails the newly created session is removed (Effect.onError).documentor/FORK.mdrecords the new divergence.Measurements
Throwaway benchmark (not committed): 234 messages = 936 rows, local Postgres 18 on disk, with an optional TCP proxy adding 1 ms each way. Before and after were interleaved because commit cost on this disk drifts.
Tests
packages/core/test/event-publish-many.test.ts(new):packages/opencode/test/session/fork-batch.test.ts(new):setTitleon another chat in the same schema;core/test/event.test.tsandsession-runner-tool-events.test.ts(one-line double update);opencode/test/session/session.test.tsandmessages-pagination.test.ts;test/pg/concurrency.test.tsandtest/pg/parity.test.ts.Not covered here
The DocuMentor side (digest pin) and a preview with a long chat come after release.
🤖 Generated with Claude Code