[Open WebUI] 필터 함수 배치 조회로 N+1 쿼리 제거
PR 링크: open-webui/open-webui#21018 상태: Merged | 변경: +24 / -12
들어가며
Open WebUI에서 채팅 요청을 처리할 때 필터 함수들을 로드하는 과정에서 전형적인 N+1 쿼리 문제가 존재했다. 필터 함수가 N개 있으면 get_function_by_id()를 N번 호출하여 N개의 개별 데이터베이스 쿼리가 발생했다. 이는 필터 함수 수가 늘어날수록 선형적으로 DB 부하가 증가하는 구조적 문제였다.
핵심 코드 분석
Before: 개별 쿼리를 리스트 컴프리헨션으로 반복 호출
filter_functions = [
Functions.get_function_by_id(filter_id)
for filter_id in get_sorted_filter_ids(
request, model, metadata.get("filter_ids", [])
)
]
필터 ID마다 get_function_by_id()를 호출하므로, 5개의 필터가 있으면 5번의 DB 쿼리가 발생한다.
After: 단일 IN 쿼리로 배치 조회
먼저 FunctionsTable에 배치 조회 메서드를 추가했다:
def get_functions_by_ids(
self, ids: list[str], db: Optional[Session] = None
) -> list[FunctionModel]:
if not ids:
return []
try:
with get_db_context(db) as db:
functions = db.query(Function).filter(Function.id.in_(ids)).all()
# Create a dict for O(1) lookup
func_dict = {f.id: FunctionModel.model_validate(f) for f in functions}
# Return in original order, filtering out any not found
return [func_dict[id] for id in ids if id in func_dict]
except Exception:
return []
그리고 호출부를 단순화했다:
filter_ids = get_sorted_filter_ids(request, model, metadata.get("filter_ids", []))
filter_functions = Functions.get_functions_by_ids(filter_ids)
왜 이게 좋은가
- 쿼리 수 감소: N개의 쿼리가 1개의
IN쿼리로 통합된다. SQL의IN절은 인덱스를 활용할 수 있어 매우 효율적이다. - 순서 보장:
dict를 활용한 O(1) 룩업으로 원래 ID 순서를 유지하면서도 성능을 해치지 않는다. - 빈 입력 방어:
ids가 비어있으면 즉시 빈 리스트를 반환하여 불필요한 DB 연결을 방지한다. - 동일 패턴 적용:
chat.py와middleware.py두 곳 모두에 같은 패턴이 적용되어 일관성 있는 개선이 이루어졌다.
참고 자료
관련 포스트
PR Analysis 의 다른글
- 이전글 [Loki] memory/columnar API를 Go 관용구에 맞게 리팩터링
- 현재글 : [Open WebUI] 필터 함수 배치 조회로 N+1 쿼리 제거
- 다음글 [uvloop] uvloop의 SSL 성능 최적화: Python Vectorcall 우회하기
댓글