Skip to content

perf: create a direct chat with one INSERT ... ON CONFLICT DO UPDATE - #58

Merged
lesnik512 merged 2 commits into
mainfrom
perf/direct-chat-upsert
Oct 17, 2026
Merged

lesnik512 merged 2 commits into
mainfrom
perf/direct-chat-upsert

Conversation

@lesnik512

@lesnik512 lesnik512 commented Oct 17, 2026 •

Copy link
Copy Markdown
Member

CreateChatUseCase handled direct chats with a fetch_direct_by_key pre-check, then an insert, then a rollback after DuplicateKeyError and a second fetch outside the Transaction block. A direct chat now uses the same statement shape as message sends (#57):

INSERT INTO chats (...) VALUES (...)
ON CONFLICT (direct_key) DO UPDATE SET direct_key = EXCLUDED.direct_key
RETURNING chats.*, xmax = 0

Members are created only when xmax = 0 reports a new chat, and fetch_with_members now runs inside the transaction, so nothing reads outside the retried unit. Group chats have no unique key and keep chats_repository.create.

Trade-off

Replay is the common path: every "open a DM with Bob" after the first. It goes from 2 statements (pre-check SELECT plus selectin) to 3 (upsert, SELECT, selectin). The first version of this PR kept replay at 2 by inserting or selecting members by hand and attaching them to the viewonly relationship. That was more machinery than one statement is worth, so it was dropped. ADR 0013 records this.

Two details that look odd

  • fetch_with_members gains populate_existing. The chat returned by the upsert is already in the identity map with its members noloaded, and without the option the selectin result would not replace that. The old code only escaped this because it fetched after the session had closed.
  • The upsert runs through wrap_sqlalchemy_exception(error_messages=DEFAULT_ERROR_MESSAGE_TEMPLATES, ...). The repository's create used to turn an unknown created_by_id into ForeignKeyError (400). A raw session.execute raises SQLAlchemy's IntegrityError instead, which becomes a 500. test_direct_chat_upsert_recovers_from_a_connection_lost_while_pooled caught this.

Tests

  • test_direct_chat_replay_starts_with_the_upsert: on main, a replay starts with a plain SELECT on chats.
  • test_direct_chat_returns_the_chat_a_concurrent_request_created_for_the_same_pair: a second real connection inserts the chat and both members without committing. The use case blocks on the lock (the test polls pg_stat_activity for wait_event_type = 'Lock'), then gets that chat back with created=False and exactly the two members. It passed repeated runs locally.
  • Removed _RacingChatsRepository, _NeverFoundChatsRepository, _AlwaysDuplicateChatsRepository and their three tests, along with fetch_direct_by_key.

Docs

ADR 0013 is new, and ADR 0005 is marked superseded_by it. ADR 0009 and AGENTS.md now describe the real-connection concurrency tests in place of the removed stubs.

Verification

Run locally against Postgres 17: ruff format, ruff check, ty check, the full suite (140 passed, 100% coverage), tests/migrations, and just adr-check.

The pre-check SELECT, the insert and the DuplicateKeyError recovery re-read
become one statement. Members are inserted in one INSERT ... RETURNING when the
chat is new and selected on a replay, then attached to the chat, so both paths
take two statements.
Members go back to the per-member create and fetch_with_members, now inside
the transaction with populate_existing. Group chats keep their old path.
@lesnik512
lesnik512 merged commit 5ef1ee2 into main Oct 17, 2026
4 checks passed
@lesnik512
lesnik512 deleted the perf/direct-chat-upsert branch October 17, 2026 07:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant