perf: stop scanning every skill on each listing and chat turn (#28798)
Listing skills ran one database query per skill in the instance. A non-admin opening the list on a workspace with 500 skills issued over 500 queries, the paginated list re-resolved the caller's group membership once per row, and every chat message carrying a skill loaded every skill the user can read, full body and owner included, to use the two or three it actually referenced. Skills now arrive already filtered: the owner-or-grant check runs in the query as an EXISTS subquery, the same way prompts and the search endpoints already do it, the per-item write flag uses the existing batch grant lookup, and the chat path asks only for the skill ids the request names. Measured with 500 skills of which 3 are visible to the caller: 504 queries and ~300 ms before, 4 queries and ~2.6 ms after. The resulting set is unchanged for owner, public, direct-user, group and multi-grant entries, for both read and write.
This commit is contained in:
@@ -163,9 +163,30 @@ class SkillsTable:
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
async def get_skills(self, db: Optional[AsyncSession] = None) -> list[SkillUserModel]:
|
||||
async def get_skills(
|
||||
self,
|
||||
user_id: str | None = None,
|
||||
ids: list[str] | None = None,
|
||||
db: AsyncSession | None = None,
|
||||
) -> list[SkillUserModel]:
|
||||
async with get_async_db_context(db) as db:
|
||||
result = await db.execute(select(Skill).order_by(Skill.updated_at.desc()))
|
||||
stmt = select(Skill).order_by(Skill.updated_at.desc())
|
||||
|
||||
if ids is not None:
|
||||
stmt = stmt.filter(Skill.id.in_(ids))
|
||||
|
||||
if user_id is not None:
|
||||
user_group_ids = {group.id for group in await Groups.get_groups_by_member_id(user_id, db=db)}
|
||||
stmt = AccessGrants.has_permission_filter(
|
||||
db=db,
|
||||
query=stmt,
|
||||
DocumentModel=Skill,
|
||||
filter={'user_id': user_id, 'group_ids': user_group_ids},
|
||||
resource_type='skill',
|
||||
permission='read',
|
||||
)
|
||||
|
||||
result = await db.execute(stmt)
|
||||
all_skills = result.scalars().all()
|
||||
|
||||
user_ids = list(set(skill.user_id for skill in all_skills))
|
||||
@@ -194,28 +215,6 @@ class SkillsTable:
|
||||
)
|
||||
return skills
|
||||
|
||||
async def get_skills_by_user_id(
|
||||
self, user_id: str, permission: str = 'write', db: Optional[AsyncSession] = None
|
||||
) -> list[SkillUserModel]:
|
||||
skills = await self.get_skills(db=db)
|
||||
user_groups = await Groups.get_groups_by_member_id(user_id, db=db)
|
||||
user_group_ids = {group.id for group in user_groups}
|
||||
|
||||
result = []
|
||||
for skill in skills:
|
||||
if skill.user_id == user_id:
|
||||
result.append(skill)
|
||||
elif await AccessGrants.has_access(
|
||||
user_id=user_id,
|
||||
resource_type='skill',
|
||||
resource_id=skill.id,
|
||||
permission=permission,
|
||||
user_group_ids=user_group_ids,
|
||||
db=db,
|
||||
):
|
||||
result.append(skill)
|
||||
return result
|
||||
|
||||
async def search_skills(
|
||||
self,
|
||||
user_id: str,
|
||||
|
||||
Reference in New Issue
Block a user