open-webui/backend/open_webui/models
Classic298 45e23c3ad0
perf: eliminate 2 redundant full chat deserialization on every message send (#21596)
* perf: eliminate 2 redundant full chat deserialization on every message send (#162)

Problem:
Every message send triggered get_chat_by_id_and_user_id which loads the
entire Chat row — including the potentially massive JSON blob containing
the full conversation history — even when the caller only needed a
simple yes/no ownership check or a single column value.

Two call sites in the message-send hot path were doing this:

1. main.py ownership verification: loaded the entire chat object including
   all message history JSON, then checked `if chat is None`. The JSON blob
   was immediately discarded — only the existence of the row mattered.

2. middleware.py folder check: loaded the entire chat object including all
   message history JSON, then read only `chat.folder_id` — a plain column
   on the chat table that requires zero JSON parsing.

Fix:
- Added `chat_exists_by_id_and_user_id()`: uses SQL EXISTS subquery which
  returns a boolean without loading any row data. The database can satisfy
  this from the primary key index alone.

- Added `get_chat_folder_id()`: queries only the `folder_id` column via
  `db.query(Chat.folder_id)`, which tells SQLAlchemy to SELECT only that
  single column instead of the entire row.

Both new methods preserve the same error handling semantics (return
False/None on exception) and user_id filtering (ownership check) as
the original get_chat_by_id_and_user_id.

Impact:
- Best case (typical): eliminates deserializing 2 full chat JSON blobs per
  message send. For long conversations (hundreds of messages with tool
  calls, images, file attachments), this blob can be multiple megabytes.
- Worst case: no regression — the new queries are strictly cheaper than
  the old ones (less data transferred, less Python object construction,
  no Pydantic model_validate overhead).
- The 3 remaining full chat loads in process_chat_payload (load_messages_from_db,
  add_file_context, chat_image_generation_handler) are left untouched as
  they genuinely need the full history and require separate analysis.

* Address maintainer feedback: rename method and inline call (#166)

- Rename chat_exists_by_id_and_user_id -> is_chat_owner
- Remove intermediate chat_owned variable; call is_chat_owner directly in if condition
2026-02-21 14:53:31 -06:00
..
access_grants.py refac 2026-02-19 16:53:21 -06:00
auths.py refac 2026-02-11 16:16:41 -06:00
channels.py refac 2026-02-19 16:53:21 -06:00
chat_messages.py fix: PostgreSQL cannot use get_chat_ids_by_model_id 2026-02-13 11:20:26 -06:00
chats.py perf: eliminate 2 redundant full chat deserialization on every message send (#21596) 2026-02-21 14:53:31 -06:00
feedbacks.py enh: analytics model modal 2026-02-04 23:42:46 -06:00
files.py chore: format 2026-02-11 16:24:11 -06:00
folders.py chore: format 2026-02-11 16:24:11 -06:00
functions.py chore: format 2026-02-11 16:24:11 -06:00
groups.py refac 2026-02-16 13:14:40 -06:00
knowledge.py refac 2026-02-19 16:53:21 -06:00
memories.py perf: eliminate redundant query after memory update (#21013) 2026-01-29 21:45:09 +04:00
messages.py feat: channel webhooks 2026-01-09 02:30:15 +04:00
models.py refac 2026-02-19 16:53:21 -06:00
notes.py refac 2026-02-19 16:53:21 -06:00
oauth_sessions.py refac: oauth session management 2026-02-20 16:49:43 -06:00
prompt_history.py chore: format 2026-02-11 16:24:11 -06:00
prompts.py refac 2026-02-19 16:53:21 -06:00
skills.py refac 2026-02-19 16:53:21 -06:00
tags.py chore: format 2026-02-16 00:43:32 -06:00
tools.py refac 2026-02-19 16:53:21 -06:00
users.py chore: format 2026-02-13 15:00:39 -06:00