From f4c6a76651cd21fad871e916807e847d611abe4e Mon Sep 17 00:00:00 2001 From: Classic298 <27028174+Classic298@users.noreply.github.com> Date: Fri, 31 Jul 2026 23:24:57 +0200 Subject: [PATCH] perf: use the orjson codec for Valkey vector metadata (#27805) The Valkey backend serializes chunk metadata on every insert and parses it back on every result row in `get` and `query`. Both directions now go through `JSONCodec`, which selects orjson when `ENABLE_ORJSON` is set. The stored `metadata_json` field is never matched against as text. `_build_filter_expression` only emits TAG predicates, and the TAG fields are `id`, `hash`, `file_id`, `source` and `knowledge_base_id`; `metadata_json` appears only as a return field that is immediately re-parsed. So rows written with escaped non-ASCII and rows written raw are indistinguishable to every reader, and no migration is needed. `process_metadata` already stringifies datetimes and strips null bytes and lone surrogates before the write, so the two backends cannot disagree about what is serializable here. Both read `except` clauses widen from `(json.JSONDecodeError, TypeError)` to `(ValueError, TypeError)`. The codec falls back to engineio's codec, which installs `parse_int=_safe_int` and raises a bare `ValueError` for integer literals longer than 100 characters; the narrower clause would have let that escape and abort a search instead of yielding empty metadata. `json.JSONDecodeError` is a `ValueError` subclass, so this is a strict superset. That removes the module's last use of stdlib `json`, so the import goes with it. With `ENABLE_ORJSON` unset, which is the default, `JSONCodec` is stdlib `json` and this call site behaves exactly as before. --- backend/open_webui/retrieval/vector/dbs/valkey.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/backend/open_webui/retrieval/vector/dbs/valkey.py b/backend/open_webui/retrieval/vector/dbs/valkey.py index 1db10281a4..950ccf969f 100644 --- a/backend/open_webui/retrieval/vector/dbs/valkey.py +++ b/backend/open_webui/retrieval/vector/dbs/valkey.py @@ -2,7 +2,6 @@ # Requires Valkey core >= 9.0.1 with the valkey-search module >= 1.2.0 loaded. import atexit -import json import logging import re import struct @@ -24,6 +23,7 @@ from open_webui.retrieval.vector.main import ( VectorItem, ) from open_webui.retrieval.vector.utils import process_metadata +from open_webui.utils.json_codec import JSONCodec log = logging.getLogger(__name__) @@ -482,7 +482,7 @@ class ValkeyClient(VectorDBBase): 'id': item['id'], 'vector': _vector_to_bytes(item['vector']), 'text': item['text'], - 'metadata_json': json.dumps(metadata), + 'metadata_json': JSONCodec.dumps(metadata), # `or ''` prevents indexing literal 'None' as a TAG value, which would # poison $ne / equality queries. 'hash': str(metadata.get('hash') or ''), @@ -588,8 +588,8 @@ class ValkeyClient(VectorDBBase): ids.append(_decode(fields.get(b'id', b''))) documents.append(_decode(fields.get(b'text', b''))) try: - metadatas.append(json.loads(_decode(fields.get(b'metadata_json', b'{}')))) - except (json.JSONDecodeError, TypeError): + metadatas.append(JSONCodec.loads(_decode(fields.get(b'metadata_json', b'{}')))) + except (ValueError, TypeError): metadatas.append({}) if limit is not None and limit > 0 and len(ids) >= limit: return GetResult(ids=[ids], documents=[documents], metadatas=[metadatas]) @@ -734,8 +734,8 @@ class ValkeyClient(VectorDBBase): ids.append(_decode(fields.get(b'id', b''))) documents.append(_decode(fields.get(b'text', b''))) try: - metadatas.append(json.loads(_decode(fields.get(b'metadata_json', b'{}')))) - except (json.JSONDecodeError, TypeError): + metadatas.append(JSONCodec.loads(_decode(fields.get(b'metadata_json', b'{}')))) + except (ValueError, TypeError): metadatas.append({}) if include_score: