[Open WebUI] DB 쿼리 최적화: 루프 삭제를 벌크 연산으로 교체
PR 링크: open-webui/open-webui#21019 상태: Merged | 변경: +12 / -20
들어가며
ORM을 사용하다 보면 편의를 위해 비효율적인 패턴이 코드에 자리 잡는 경우가 많습니다. 대표적으로 "전체 조회 후 루프 삭제"와 "업데이트 후 불필요한 재조회"가 있습니다. 이번 PR은 Open WebUI의 세 가지 모델 파일에서 이러한 패턴을 일괄적으로 정리합니다.
핵심 코드 분석
루프 기반 삭제를 벌크 DELETE로 교체
Before (feedbacks.py):
def delete_feedbacks_by_user_id(self, user_id: str, db=None) -> bool:
with get_db_context(db) as db:
feedbacks = db.query(Feedback).filter_by(user_id=user_id).all()
if not feedbacks:
return False
for feedback in feedbacks:
db.delete(feedback)
db.commit()
return True
After:
def delete_feedbacks_by_user_id(self, user_id: str, db=None) -> bool:
with get_db_context(db) as db:
result = db.query(Feedback).filter_by(user_id=user_id).delete()
db.commit()
return result > 0
N개의 행을 삭제할 때 기존에는 먼저 SELECT *로 전체 행을 Python 메모리에 로드한 뒤, 각 객체에 대해 개별 DELETE SQL을 발행했습니다. 변경 후에는 단일 DELETE FROM ... WHERE 쿼리 하나로 처리됩니다.
그룹 멤버 제거를 IN 절로 배치 처리
Before (groups.py):
for user_id in user_ids:
db.query(GroupMember).filter(
GroupMember.group_id == id, GroupMember.user_id == user_id
).delete()
After:
db.query(GroupMember).filter(
GroupMember.group_id == id, GroupMember.user_id.in_(user_ids)
).delete(synchronize_session=False)
업데이트 후 불필요한 재조회 제거
Before (functions.py):
db.commit()
db.refresh(function)
return self.get_function_by_id(id, db=db)
After:
db.commit()
db.refresh(function)
return FunctionModel.model_validate(function)
db.refresh()가 이미 DB에서 최신 상태를 가져왔으므로, get_function_by_id를 다시 호출하여 동일한 행을 한 번 더 SELECT할 필요가 없습니다.
왜 이게 좋은가
- functions.py: 업데이트 연산당 불필요한 SELECT 쿼리 1회 절감
- feedbacks.py: N개 행 삭제 시 N+1개의 SQL(SELECT 1 + DELETE N)이 단일 DELETE 1개로 감소
- groups.py: 멤버 N명 제거 시 DELETE N개가 IN 절을 사용한 DELETE 1개로 감소
- ORM의 편의성을 유지하면서도 실제 발행되는 SQL 수를 최소화하는 좋은 패턴입니다.
참고 자료
관련 포스트
PR Analysis 의 다른글
- 이전글 [Open WebUI] SCIM 그룹 변환에서 N+1 쿼리를 배치 조회로 제거
- 현재글 : [Open WebUI] DB 쿼리 최적화: 루프 삭제를 벌크 연산으로 교체
- 다음글 [Open WebUI] 메모리 업데이트 후 불필요한 재조회 쿼리 제거
댓글