mirror of
https://github.com/open-webui/open-webui.git
synced 2026-05-06 02:48:13 -05:00
[GH-ISSUE #23192] issue: Runtime OOM: chat.chat JSON blob fully loaded into memory on every chat open — no column projection in read path #35444
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Originally created by @soonlaii-upskill on GitHub (Mar 29, 2026).
Original GitHub issue: https://github.com/open-webui/open-webui/issues/23192
Check Existing Issues
Installation Method
Docker
Open WebUI Version
v0.8.10
Ollama Version (if applicable)
No response
Operating System
Docker (Linux container)
Browser (if applicable)
No response
Confirmation
README.md.Expected Behavior
Opening a chat or running a search should use memory proportional to the response payload, not the full stored blob size. A single large chat row should not be able to OOM-kill the server for all users.
Actual Behavior
Our production server is OOM-killed (Signal 9). We have a
chatcolumn row of 230,153,522 characters (~219 MB) — a single long-running conversation. Every read path inmodels/chats.pyloads this full blob into Python memory with no column projection.Memory lifecycle per request on that row:
_sanitize_chat_row()insideget_chat_by_idrecursively copies the entire dict again (second full copy, designed for write-time null-byte cleaning)model_validatecreates a third copymodel_dump()+ JSON serialization = fourth copyPeak RSS: ~1–2 GB per request on one 219 MB row. Multiple concurrent users = OOM-kill.
Beyond single-chat opens, these functions are also affected:
get_chats()—.limit(limit).offset(skip)is commented out. The admin endpointGET /api/v1/chats/allcalls this, loading ALL rows with full blobs (5,149 rows × avg 2 MB = ~10 GB).get_chats_by_user_id_and_search_text()— SQLWHEREfiltering is correct (usesjson_each/json_array_elementsin-DB), but matching rows are fetched as full ORM objects. A search that matches the 219 MB row loads it fully just to return a title.get_archived_chats_by_user_id()— no limit, no projection, returnslist[ChatModel]with full blobs.get_chats_by_folder_ids_and_user_id()— no limit, no projection.get_chat_list_by_user_id_and_tag_name()— no limit, no projection.get_chat_list_by_user_id()— returnslist[ChatModel](full blobs) even for sidebar rendering which only needs id/title/timestamps.Note: these 4 functions already do it correctly using
.with_entities()—get_chat_title_id_list_by_user_id,get_pinned_chats_by_user_id,get_archived_chat_list_by_user_id,get_shared_chat_list_by_user_id. The pattern exists, it just needs applying consistently.Steps to Reproduce
sources— realistic after months of daily usechatcolumn sizeLogs & Screenshots
1. Pod describe — OOMKill confirmation
2. Last log lines before process death
No Python exception, no traceback — process is killed at OS level with no warning:
3. Database blob distribution
4. Subprocess OOMKill proof — loading largest blob kills the exec itself
The
kubectl execsubprocess itself is OOMKilled beforejson.loads()returns. This is not a Python exception — it is the kernel reclaiming memory.5. Cgroup memory readings (
/sys/fs/cgroup/memory.current)Pod came within ~32 MB of the 4 GiB limit during normal use. Prior limit was 2 GiB — explaining the kills.
6. Top 5 blobs by size
One user owns 3 of the top 5 blobs (~440 MB combined). Every
GET /api/v1/chats/call by this user loads all of them.Additional Information
This is distinct from the migration OOM fixed in PR #21542. That fix correctly addresses the
8452d01d26d7Alembic migration (we already have it). This issue is the runtime read path — it exists in every version afterchat_messagewas introduced because the read path was never switched away from the blob.The long-term fix is switching
get_chat_by_idto read fromchat_message WHERE chat_id = ?instead of loading the blob — the dual-write is already fully in place. The immediate fix is applying.with_entities()projection to list endpoints (same pattern already used in 4 existing functions) and removing_sanitize_chat_rowfrom the read path (null-byte sanitization belongs at write time only).@ShirasawaSama commented on GitHub (Mar 29, 2026):
#22206