mirror of
https://github.com/open-webui/open-webui.git
synced 2026-07-16 14:39:31 -05:00
[PR #24025] [CLOSED] perf(chats): skip deep copy in sanitize_data_for_db when input has no null bytes or surrogates #82355
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?
📋 Pull Request Information
Original PR: https://github.com/open-webui/open-webui/pull/24025
Author: @runixer
Created: 4/23/2026
Status: ❌ Closed
Base:
dev← Head:perf/sanitize-data-for-db-fast-path📝 Commits (1)
e5350d7perf(chats): skip deep copy in sanitize_data_for_db when input has no null bytes or surrogates📊 Changes
1 file changed (+31 additions, -0 deletions)
View changed files
📝
backend/open_webui/utils/misc.py(+31 -0)📄 Description
Pull Request Checklist
devdevChangelog Entry
sanitize_data_for_db. The chat write path no longer rebuilds the entire chat structure on every call tosanitize_data_for_db. A lightweight scan now checks whether any string actually contains the characters thatsanitize_text_for_dbwould rewrite (NUL or lone UTF-16 surrogates); if not, the input is returned as-is. This eliminates a deep copy of the chat JSON on effectively every real-world write.Description
sanitize_data_for_dbis called on every chat payload before persisting. The existing implementation always rebuilds the whole structure recursively — new dicts, new lists, re-sanitized strings — even when the input is already clean. That allocates a parallel copy of the entire chat tree, which becomes tens of MB on long RAG-heavy conversations.In practice the characters that
sanitize_text_for_dbrewrites (NUL and lone UTF-16 surrogate code points) cannot be stored in PostgreSQLtextorjsonbcolumns, so data read back from the database is always clean. The only path that produces dirty input is inbound untrusted content on ingest, which is extremely rare.This PR adds a
_sanitize_needs_copyhelper that scans for exactly the code pointssanitize_text_for_dbwould modify. If nothing matches,sanitize_data_for_dbreturns the input object as-is (zero copy). If anything matches, it falls back to the existing recursive-copy path. The behaviour is semantically identical for all inputs.Measurements on real data
Against a 34 780-row production chat table (with
pg_column_size(chat)up to 80 MB):Typical chats (many short leaves, deep nesting) are 1.5–1.8× faster and allocate nothing on sanitize. Pathological shapes with a handful of huge leaf strings get slower CPU-wise (regex scan vs C-level
str.replace+encode/decode) but still save on memory.Correctness
Invariant:
new_sanitize(x) == old_sanitize(x)for everyx.Verified by running both implementations against every chat in our production database (34,780 rows) and deep-comparing outputs. 0 mismatches. 99.98% of chats took the fast path; the 7 that fell through to the slow path all contained legacy NUL bytes (binary pasted into messages, or ZIP bytes from pre-Tika document uploads before
sanitize_data_for_dbwas introduced in #20072) and produced identical results in both implementations.Testing
Running in our production fork since 2026-04-20. The patch only changes the pre-check and preserves the original recursive path verbatim, so behaviour for any dirty input is unchanged.
Contributor License Agreement
🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.