From d93cb3658dae98499af10946444de54b572e8e71 Mon Sep 17 00:00:00 2001 From: Classic298 <27028174+Classic298@users.noreply.github.com> Date: Fri, 6 Mar 2026 22:56:01 +0100 Subject: [PATCH] perf(models): batch-fetch function valves to eliminate N+1 queries (#22301) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * perf(models): batch-fetch function valves to eliminate N+1 queries get_action_priority() called Functions.get_function_valves_by_id() individually for every action on every model — an N+1 query pattern that issued one DB round-trip per (action x model) pair. Add Functions.get_function_valves_by_ids() that fetches all valves in a single WHERE IN query, then look up each action's valves from the pre-fetched dict inside get_action_priority(). No functional change — same priority resolution, same sort order. * Update models.py * Update models.py --- backend/open_webui/models/functions.py | 25 +++++++++++++++++++++++++ backend/open_webui/utils/models.py | 8 +++++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/backend/open_webui/models/functions.py b/backend/open_webui/models/functions.py index fdbfac5e7..bca1223b7 100644 --- a/backend/open_webui/models/functions.py +++ b/backend/open_webui/models/functions.py @@ -308,6 +308,31 @@ class FunctionsTable: log.exception(f"Error getting function valves by id {id}: {e}") return None + def get_function_valves_by_ids( + self, ids: list[str], db: Optional[Session] = None + ) -> dict[str, dict]: + """ + Batch fetch valves for multiple functions in a single query. + Returns a dict mapping function_id -> valves dict. + Functions without valves are mapped to {}. + """ + if not ids: + return {} + try: + with get_db_context(db) as db: + functions = ( + db.query(Function.id, Function.valves) + .filter(Function.id.in_(ids)) + .all() + ) + return { + f.id: (f.valves if f.valves else {}) for f in functions + } + except Exception as e: + log.exception(f"Error batch-fetching function valves: {e}") + return {} + + def update_function_valves_by_id( self, id: str, valves: dict, db: Optional[Session] = None ) -> Optional[FunctionValves]: diff --git a/backend/open_webui/utils/models.py b/backend/open_webui/utils/models.py index 6c82b4aa9..ebf855d2c 100644 --- a/backend/open_webui/utils/models.py +++ b/backend/open_webui/utils/models.py @@ -331,11 +331,17 @@ async def get_all_models(request, refresh: bool = False, user: UserModel = None) elif meta.get(key) is None: meta[key] = copy.deepcopy(value) + # Batch-fetch all function valves in one query to avoid N+1 DB hits + # inside get_action_priority (previously called per action × per model). + all_function_valves = Functions.get_function_valves_by_ids( + list(all_function_ids) + ) + def get_action_priority(action_id): try: function_module = request.app.state.FUNCTIONS.get(action_id) if function_module and hasattr(function_module, "Valves"): - valves_db = Functions.get_function_valves_by_id(action_id) + valves_db = all_function_valves.get(action_id) valves = function_module.Valves(**(valves_db if valves_db else {})) return getattr(valves, "priority", 0) except Exception: