Repository navigation
Conversation
ChatMachine.Update seeds the transaction with the row returned by LockChatAndBumpSnapshotVersion plus a queue count taken right after it. Callbacks read it with tx.Current() and the first transition validates against it, so neither re-reads the chat while the row lock is held. ChatMachine.UpdateReturning returns the row Update already reads after the callback for publication. updateChat (send, edit, promote, tool results, interrupt, compact, clear, reconcile), hook failure, family archive, and the worker's generation and task transitions use it instead of reloading the chat under the lock. Generated by Coder Agents on behalf of @cstyan.
|
|
||
| Each transaction that applies one or more transitions advances the `snapshot_version` field on the `chats` table by 1 immediately after locking the chat row and before mutating any tables. This lets us version the chat's execution state. The chat worker and the stream loop rely on it to ensure they do not process outdated or out of order notifications. | ||
|
|
||
| TODO (tx.Current): `ChatMachine.Update` counts queued messages right after the lock and seeds the transaction with the locked row. Callbacks read it with `tx.Current()` and the first transition validates against it, so neither reads the chat again under the lock. Later transitions in the same transaction and the publication read still go to the database. Describe this here. |
There was a problem hiding this comment.
we don't need changes in this file
| // under the lock. The row is zero if fn or that read failed. It is still | ||
| // set when the commit or publication fails afterwards, so treat it as | ||
| // committed only when err is nil. | ||
| func (m *ChatMachine) UpdateReturning( |
There was a problem hiding this comment.
I'd rather we made Update return a chat instead of adding UpdateReturning.
|
|
||
| // Current returns the chat row and execution state, reusing the locked | ||
| // row without consuming it when no transition has run yet. | ||
| func (tx *Tx) Current() (database.Chat, ExecutionState, error) { |
There was a problem hiding this comment.
The name Current() is misleading. It will return a stale chat if loadState isn't called after modifying the chat, and there's no convention to when loadState gets called.
I think the cleanest way to pass a cached chat to a transition is via an extra optional argument on the transition itself. You could define a ChatSnapshot struct that'd be an optional field on transitions' input structs. E.g.
type ChatSnapshot struct {
Chat database.Chat
HasQueued bool
}
type DeleteQueuedMessageInput struct {
*ChatSnapshot
QueuedMessageID int64
}I'd also encourage you to pass the initial chat snapshot in the callback in Update:
func (m *ChatMachine) Update(
ctx context.Context,
fn func(*Tx, database.Store, *ChatSnapshot) error,
) error {That way it's clear this snapshot is only valid at the beginning of the callback and there's no mechanism that keeps it up to date later.
One of the main costs in terms of DB load from chats is extra round trips during transactions. This results in locks being held for longer periods of time -> an increase in lock waits for chats related queries.
One of the opportunities for reducing some of the round trips is to eliminate extra chat row reads after we have just read the row in order to lock it.
In this PR
ChatMachine.Updatenow seeds the transaction struct with the row returned byLockChatAndBumpSnapshotVersion, plus a queue count taken right after it. Callbacks can then read it withtx.Current()and the first transition validates against it, so neither re-reads the chat row from the database while the row lock is held. Note that there is also a change such thatreadStatealso returns the chat row from the transaction struct, then clearing the cached chat, for operations which would then immediately write an update to the chat row.ChatMachine.UpdateReturningis new, it returns the row already read after the callback for publication.updateChat(send, edit, promote, tool results, interrupt, compact, clear, reconcile), hook failure, family archive, and the worker's generation and task transitions use it instead of reloading the chat under the lock.This PR relative to main:
Generated by Coder Agents on behalf of @cstyan.