From eff06538a65cb9685dcdf00c536e13f4996d83c2 Mon Sep 17 00:00:00 2001 From: Sihyeon Jang Date: Wed, 3 Sep 2025 05:56:48 +0900 Subject: [PATCH] perf: fix N+1 query issues in user group access control validation - Pre-fetch user group IDs in get_*_by_user_id methods across models layer - Pass user_group_ids to has_access to avoid repeated group queries - Reduce query count from 1+N to 1+1 pattern for access control validation - Apply consistent optimization across knowledge, models, notes, prompts, and tools Signed-off-by: Sihyeon Jang --- backend/open_webui/models/knowledge.py | 4 +++- backend/open_webui/models/models.py | 4 +++- backend/open_webui/models/notes.py | 4 +++- backend/open_webui/models/prompts.py | 4 +++- backend/open_webui/models/tools.py | 3 ++- 5 files changed, 14 insertions(+), 5 deletions(-) diff --git a/backend/open_webui/models/knowledge.py b/backend/open_webui/models/knowledge.py index bed3d5542e..c0f68cf34d 100644 --- a/backend/open_webui/models/knowledge.py +++ b/backend/open_webui/models/knowledge.py @@ -8,6 +8,7 @@ from open_webui.internal.db import Base, get_db from open_webui.env import SRC_LOG_LEVELS from open_webui.models.files import FileMetadataResponse +from open_webui.models.groups import Groups from open_webui.models.users import Users, UserResponse @@ -147,11 +148,12 @@ class KnowledgeTable: self, user_id: str, permission: str = "write" ) -> list[KnowledgeUserModel]: knowledge_bases = self.get_knowledge_bases() + user_group_ids = {group.id for group in Groups.get_groups_by_member_id(user_id)} return [ knowledge_base for knowledge_base in knowledge_bases if knowledge_base.user_id == user_id - or has_access(user_id, permission, knowledge_base.access_control) + or has_access(user_id, permission, knowledge_base.access_control, user_group_ids) ] def get_knowledge_by_id(self, id: str) -> Optional[KnowledgeModel]: diff --git a/backend/open_webui/models/models.py b/backend/open_webui/models/models.py index 1a29b86eae..defc02572d 100755 --- a/backend/open_webui/models/models.py +++ b/backend/open_webui/models/models.py @@ -5,6 +5,7 @@ from typing import Optional from open_webui.internal.db import Base, JSONField, get_db from open_webui.env import SRC_LOG_LEVELS +from open_webui.models.groups import Groups from open_webui.models.users import Users, UserResponse @@ -199,11 +200,12 @@ class ModelsTable: self, user_id: str, permission: str = "write" ) -> list[ModelUserResponse]: models = self.get_models() + user_group_ids = {group.id for group in Groups.get_groups_by_member_id(user_id)} return [ model for model in models if model.user_id == user_id - or has_access(user_id, permission, model.access_control) + or has_access(user_id, permission, model.access_control, user_group_ids) ] def get_model_by_id(self, id: str) -> Optional[ModelModel]: diff --git a/backend/open_webui/models/notes.py b/backend/open_webui/models/notes.py index ce3b9f2e20..c720ff80a4 100644 --- a/backend/open_webui/models/notes.py +++ b/backend/open_webui/models/notes.py @@ -4,6 +4,7 @@ import uuid from typing import Optional from open_webui.internal.db import Base, get_db +from open_webui.models.groups import Groups from open_webui.utils.access_control import has_access from open_webui.models.users import Users, UserResponse @@ -105,11 +106,12 @@ class NoteTable: self, user_id: str, permission: str = "write" ) -> list[NoteModel]: notes = self.get_notes() + user_group_ids = {group.id for group in Groups.get_groups_by_member_id(user_id)} return [ note for note in notes if note.user_id == user_id - or has_access(user_id, permission, note.access_control) + or has_access(user_id, permission, note.access_control, user_group_ids) ] def get_note_by_id(self, id: str) -> Optional[NoteModel]: diff --git a/backend/open_webui/models/prompts.py b/backend/open_webui/models/prompts.py index 8ef4cd2bec..ef85c047a0 100644 --- a/backend/open_webui/models/prompts.py +++ b/backend/open_webui/models/prompts.py @@ -2,6 +2,7 @@ import time from typing import Optional from open_webui.internal.db import Base, get_db +from open_webui.models.groups import Groups from open_webui.models.users import Users, UserResponse from pydantic import BaseModel, ConfigDict @@ -122,12 +123,13 @@ class PromptsTable: self, user_id: str, permission: str = "write" ) -> list[PromptUserResponse]: prompts = self.get_prompts() + user_group_ids = {group.id for group in Groups.get_groups_by_member_id(user_id)} return [ prompt for prompt in prompts if prompt.user_id == user_id - or has_access(user_id, permission, prompt.access_control) + or has_access(user_id, permission, prompt.access_control, user_group_ids) ] def update_prompt_by_command( diff --git a/backend/open_webui/models/tools.py b/backend/open_webui/models/tools.py index 7f1409a900..fa0fb77893 100644 --- a/backend/open_webui/models/tools.py +++ b/backend/open_webui/models/tools.py @@ -161,12 +161,13 @@ class ToolsTable: self, user_id: str, permission: str = "write" ) -> list[ToolUserModel]: tools = self.get_tools() + user_group_ids = {group.id for group in Groups.get_groups_by_member_id(user_id)} return [ tool for tool in tools if tool.user_id == user_id - or has_access(user_id, permission, tool.access_control) + or has_access(user_id, permission, tool.access_control, user_group_ids) ] def get_tool_valves_by_id(self, id: str) -> Optional[dict]: