mirror of
https://github.com/open-webui/open-webui.git
synced 2026-08-28 05:27:35 +00:00
perf: stop rescanning the whole chat JSON on every streamed event write
Every streamed event that persists to a chat (status updates, citations, file attachments, message content) serialized the entire conversation JSON three times: a null-byte check of the stored row, a second sanitize of the whole blob after merging in the event payload and the flush of the UPDATE itself. The middle pass rescans megabytes of already-clean history for null bytes that can only come from the small incoming payload, so long chats pay for their full history on every single event. The write paths now sanitize just the incoming message, message id and status dict and keep the row-level sanitize, so legacy rows with null bytes still self-heal as before. Median per-event write time (sqlite, orjson): 1 MB chat 15.0 ms to 11.1 ms, 4 MB 65.2 ms to 52.4 ms, 10 MB 159.9 ms to 124.5 ms, roughly 20 percent less per event. As a side effect the chat_message dual write now receives the sanitized message; previously null bytes in non-content fields were cleaned in the blob but written raw to chat_message, which failed that insert on PostgreSQL. Verified byte-identical rows against the previous implementation across nine scenarios covering null bytes in every input, legacy dirty rows, a missing title and a NULL chat column.
This commit is contained in:
parent
7cf6051a74
commit
af23d4e272
1 changed files with 18 additions and 21 deletions
|
|
@ -398,6 +398,9 @@ class ChatTable:
|
|||
"""
|
||||
Clean a Chat SQLAlchemy model's title + chat JSON,
|
||||
and return True if anything changed.
|
||||
|
||||
The message write paths (upsert/status/delete) rely on this
|
||||
leaving the blob clean and sanitize only the data they add.
|
||||
"""
|
||||
changed = False
|
||||
|
||||
|
|
@ -1052,9 +1055,8 @@ class ChatTable:
|
|||
if output_text:
|
||||
message['content'] = output_text
|
||||
|
||||
# Sanitize message content for null characters before upserting
|
||||
if isinstance(message.get('content'), str):
|
||||
message['content'] = sanitize_text_for_db(message['content'])
|
||||
message = self._clean_null_bytes(message)
|
||||
message_id = self._clean_null_bytes(message_id)
|
||||
|
||||
try:
|
||||
async with get_async_db_context() as session:
|
||||
|
|
@ -1069,10 +1071,9 @@ class ChatTable:
|
|||
history = chat.get('history', {})
|
||||
saved_message = self.upsert_message_to_history(history, message_id, message)
|
||||
chat['history'] = history
|
||||
clean_chat = self._clean_null_bytes(chat)
|
||||
chat_item.chat = clean_chat
|
||||
chat_item.title = self._clean_null_bytes(clean_chat['title']) if 'title' in clean_chat else 'New Chat'
|
||||
chat_item.current_message_id = self.get_current_message_id(clean_chat)
|
||||
chat_item.chat = chat # chat is a fresh dict when the column was empty
|
||||
chat_item.title = chat.get('title', 'New Chat')
|
||||
chat_item.current_message_id = self.get_current_message_id(chat)
|
||||
flag_modified(chat_item, 'chat')
|
||||
|
||||
if touch:
|
||||
|
|
@ -1111,22 +1112,18 @@ class ChatTable:
|
|||
history = chat.get('history', {})
|
||||
deleted_ids = self.delete_message_from_history(history, message_id)
|
||||
if not deleted_ids:
|
||||
clean_chat = self._clean_null_bytes(chat)
|
||||
chat_item.chat = clean_chat
|
||||
chat_item.title = (
|
||||
self._clean_null_bytes(clean_chat['title']) if 'title' in clean_chat else 'New Chat'
|
||||
)
|
||||
chat_item.current_message_id = self.get_current_message_id(clean_chat)
|
||||
chat_item.chat = chat
|
||||
chat_item.title = chat.get('title', 'New Chat')
|
||||
chat_item.current_message_id = self.get_current_message_id(chat)
|
||||
flag_modified(chat_item, 'chat')
|
||||
await session.commit()
|
||||
return ChatModel.model_validate(chat_item)
|
||||
|
||||
messages = history.get('messages') or {}
|
||||
chat['history'] = history
|
||||
clean_chat = self._clean_null_bytes(chat)
|
||||
chat_item.chat = clean_chat
|
||||
chat_item.title = self._clean_null_bytes(clean_chat['title']) if 'title' in clean_chat else 'New Chat'
|
||||
chat_item.current_message_id = self.get_current_message_id(clean_chat)
|
||||
chat_item.chat = chat
|
||||
chat_item.title = chat.get('title', 'New Chat')
|
||||
chat_item.current_message_id = self.get_current_message_id(chat)
|
||||
flag_modified(chat_item, 'chat')
|
||||
chat_item.updated_at = int(time.time())
|
||||
await session.commit()
|
||||
|
|
@ -1144,6 +1141,7 @@ class ChatTable:
|
|||
self, id: str, message_id: str, status: dict
|
||||
) -> ChatModel | None:
|
||||
try:
|
||||
status = self._clean_null_bytes(status)
|
||||
async with get_async_db_context() as session:
|
||||
chat_item = await session.get(Chat, id)
|
||||
if chat_item is None:
|
||||
|
|
@ -1160,10 +1158,9 @@ class ChatTable:
|
|||
history['messages'][message_id]['statusHistory'] = status_history
|
||||
|
||||
chat['history'] = history
|
||||
clean_chat = self._clean_null_bytes(chat)
|
||||
chat_item.chat = clean_chat
|
||||
chat_item.title = self._clean_null_bytes(clean_chat['title']) if 'title' in clean_chat else 'New Chat'
|
||||
chat_item.current_message_id = self.get_current_message_id(clean_chat)
|
||||
chat_item.chat = chat
|
||||
chat_item.title = chat.get('title', 'New Chat')
|
||||
chat_item.current_message_id = self.get_current_message_id(chat)
|
||||
flag_modified(chat_item, 'chat')
|
||||
await session.commit()
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue