Skip to content

feat: replace InsertChatMessage with batch InsertChatMessages#23220

Merged
kylecarbs merged 1 commit into
mainfrom
kylecarbs/insert-chat-messages
Mar 18, 2026
Merged

feat: replace InsertChatMessage with batch InsertChatMessages#23220
kylecarbs merged 1 commit into
mainfrom
kylecarbs/insert-chat-messages

Conversation

@kylecarbs
Copy link
Copy Markdown
Member

Replaces the singular InsertChatMessage query with InsertChatMessages that uses PostgreSQL's unnest() for batch inserts. This reduces the number of database round-trips when inserting multiple messages in a single transaction.

Changes

  • SQL: New InsertChatMessages :many query using unnest() arrays following the existing codebase pattern (e.g., InsertWorkspaceAgentStats). Preserves the CTE that updates chats.last_model_config_id using the last non-null model config from the batch. Uses NULLIF for UUID columns to handle NULL foreign keys.
  • Go layers: Updated querier.go, dbauthz.go, dbmetrics/querymetrics.go, dbmock/dbmock.go, and queries.sql.go to use the new batch signature ([]ChatMessage return type, array params).
  • chatd.go: All call sites converted to batch inserts:
    • CreateChat: System prompt + user message batched into one call
    • persistStep: Assistant message + tool messages batched into one call
    • persistSummary: Hidden summary + assistant + tool messages batched into one call
    • Single-message sites use the same API with single-element arrays
  • Helper: New appendChatMessage function simplifies building batch params at each call site.
  • Tests: All test files updated to use the new API.

Builds on top of #23213.

@kylecarbs kylecarbs requested a review from ethanndickson March 18, 2026 14:11
@kylecarbs kylecarbs force-pushed the kylecarbs/insert-chat-messages branch 3 times, most recently from 6fb88ce to 05bc921 Compare March 18, 2026 14:43
Comment thread coderd/database/queries/chats.sql
Comment thread coderd/chatd/chatd.go
@kylecarbs kylecarbs force-pushed the kylecarbs/insert-chat-messages branch 2 times, most recently from 5f07b45 to 0042628 Compare March 18, 2026 15:34
@kylecarbs kylecarbs requested a review from ethanndickson March 18, 2026 15:35
@kylecarbs kylecarbs force-pushed the kylecarbs/insert-chat-messages branch from 0042628 to fdb99c1 Compare March 18, 2026 15:43
Replace the singular InsertChatMessage query with InsertChatMessages
that uses PostgreSQL's unnest() for batch inserts. This reduces the
number of round-trips to the database when inserting multiple messages
in a single transaction (e.g., chat creation with system+user messages,
persisting assistant+tool messages, and context compression summaries).

The new query:
- Uses unnest() arrays for all parameters following the existing
  codebase pattern (e.g., InsertWorkspaceAgentStats).
- Preserves the IS DISTINCT FROM optimization in the CTE that updates
  chats.last_model_config_id.
- Uses NULLIF for UUID columns to properly handle NULL foreign keys.
- Uses NULLIF(..., 0) for nullable numeric columns (tokens, cost,
  runtime_ms) to preserve NULL semantics used by cost reporting.
- Uses text[] for content with a ::jsonb cast so pq.Array serializes
  correctly.
- Returns all inserted rows.

A chatMessage struct and appendChatMessage helper simplify building
the batch params at each call site with named fields.
@kylecarbs kylecarbs force-pushed the kylecarbs/insert-chat-messages branch from fdb99c1 to df8ad47 Compare March 18, 2026 16:00
@kylecarbs kylecarbs enabled auto-merge (squash) March 18, 2026 16:25
Copy link
Copy Markdown
Member

@johnstcn johnstcn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coulple of non-blocking comments.

Comment thread coderd/chats_test.go
Comment on lines 4064 to +4085
for i := 0; i < 2; i++ {
message, err := db.InsertChatMessage(dbauthz.AsSystemRestricted(ctx), database.InsertChatMessageParams{
ChatID: chat.ID,
ModelConfigID: uuid.NullUUID{UUID: modelConfig.ID, Valid: true},
Role: "assistant",
Visibility: database.ChatMessageVisibilityBoth,
InputTokens: sql.NullInt64{Int64: 100, Valid: true},
OutputTokens: sql.NullInt64{Int64: 50, Valid: true},
TotalCostMicros: sql.NullInt64{Int64: 500, Valid: true},
})
require.NoError(t, err)
results, err := db.InsertChatMessages(dbauthz.AsSystemRestricted(ctx), database.InsertChatMessagesParams{
ChatID: chat.ID,
CreatedBy: []uuid.UUID{uuid.Nil},
ModelConfigID: []uuid.UUID{modelConfig.ID},
Role: []database.ChatMessageRole{"assistant"},
Content: []string{"null"},
ContentVersion: []int16{0},
Visibility: []database.ChatMessageVisibility{database.ChatMessageVisibilityBoth},
InputTokens: []int64{100},
OutputTokens: []int64{50},
TotalTokens: []int64{0},
ReasoningTokens: []int64{0},
CacheCreationTokens: []int64{0},
CacheReadTokens: []int64{0},
ContextLimit: []int64{0},
Compressed: []bool{false},
TotalCostMicros: []int64{500},
RuntimeMs: []int64{0},
})
require.NoError(t, err)
message := results[0]
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should actually exercise the batch logic here instead of looping

RawMessage: json.RawMessage(fmt.Sprintf("%q", content)),
Valid: true,
},
_, err := store.InsertChatMessages(ctx, database.InsertChatMessagesParams{
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should test with multiple messages

Comment on lines +233 to +234
unnest(@role::chat_message_role[]),
unnest(@content::text[])::jsonb,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: CASING

Comment thread coderd/chatd/chatd.go
// represent NULL — the SQL uses NULLIF to convert zero UUIDs to NULL.
// For nullable int64 fields, use 0 to represent NULL — the SQL uses
// NULLIF to convert zeros to NULL.
type chatMessage struct {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: builder pattern would be nice here for nullable/optional fields

@kylecarbs kylecarbs merged commit 483adc5 into main Mar 18, 2026
26 checks passed
@kylecarbs kylecarbs deleted the kylecarbs/insert-chat-messages branch March 18, 2026 16:27
@github-actions github-actions Bot locked and limited conversation to collaborators Mar 18, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants