Skip to content

[Fix] Generic Plan에서 질문 Vector를 행마다 변환해 벡터 검색이 느려지는 문제 - #550

Merged
Gimini-3 merged 5 commits into
developfrom
fix/549
Oct 17, 2026
Merged

Gimini-3 merged 5 commits into
developfrom
fix/549

Conversation

@Gimini-3

Copy link
Copy Markdown
Contributor

🔍️ 작업 내용

#545(PR #547) 측정에서 발견한 지연 문제를 고칩니다. 같은 벡터 검색 SQL을 6번 넘게 실행해 PostgreSQL이 Generic Plan을 쓰면, CAST(:queryVector AS vector)가 행마다 실행돼 1024차원 문자열을 검색 대상 청크 수만큼 파싱했습니다. 질문 Vector를 스칼라 서브쿼리로 감싸 InitPlan으로 실행마다 한 번만 계산하게 했습니다.

-- 수정 전
ORDER BY e.vector <=> CAST(:queryVector AS vector)
-- 수정 후
ORDER BY e.vector <=> (SELECT CAST(:queryVector AS vector))

✨ 상세 설명

수정 전후 성능 (#545와 같은 환경)

5만 청크·topK 10, 질문 30개 p50입니다. 같은 SQL이 반복돼 Generic Plan이 쓰일 때의 값입니다.

허용 비율 벡터 단독 검색 개선율 하이브리드 검색(기본 경로) 개선율
100% 4,226.3 → 207.4ms 95.1% (20.4배) 5,444.6 → 1,321.6ms 75.7%
10% 480.5 → 24.9ms 94.8% 609.4 → 149.2ms 75.5%
1% 43.2 → 6.8ms 84.4% 56.3 → 19.0ms 66.2%
0.1% 7.9 → 3.7ms 53.4% 10.9 → 6.6ms 39.0%
  • 수정 후 Generic Plan은 Custom Plan(처음 5번)과 같은 속도입니다. 100%에서 Generic 207.4ms, Custom 208.9ms입니다.
  • psql auto 모드 반복: 수정 전에는 6회째부터 145ms → 2,216ms로 뛰었고, 수정 후에는 18회 내내 142163ms입니다.
  • 검색 결과는 같습니다. Exact·현재 계획(Custom·Generic) 2,160개 질문·조합의 SQL 후보·최종 결과·Recall이 수정 전과 모두 같습니다.
    • HNSW 강제 방식에서 보인 차이는 측정마다 Index를 새로 만들기 때문입니다. 같은 Index 위에서는 수정 전/후 결과가 120/120 같았습니다.
  • Custom Plan·HNSW 계획은 사실상 그대로입니다. 같은 Index에서 번갈아 15회 잰 변화가 -1.2~+2.0%(HNSW 계획은 +0.1ms)입니다.

측정 방법

  • #545와 같은 격리 컨테이너 이미지 digest, PostgreSQL 기본 설정, 같은 원본 251건·Embedding·Seed·질문 30개, 같은 하네스(PermissionRecallBenchmark)를 썼습니다. 5만 청크 HNSW Index 크기가 #545와 바이트까지 같습니다.
  • A/B: 수정 전 코드와 수정 후 코드를 연달아 측정했습니다(5만 청크·topK 10·Exact·현재 계획).
  • 전체 재측정: 수정 후 코드로 192개 조합을 다시 재서 [Test] 권한 필터에 따른 벡터 검색 누락 재현과 인덱스 방식 비교 (로컬 pgvector) #545 기준과 비교했습니다.
  • psql: auto 모드 8회 반복, 같은 HNSW Index에서 수정 전/후 SQL 비교를 했습니다.

테스트

  • 회귀 통합 테스트 VectorSearchRepositoryGenericPlanIntegrationTest 3건(운영 @Query SQL을 그대로 PREPARE)
    • Generic Plan 결과가 Custom Plan과 같은지: 수정 전·후 모두 통과
    • Generic Plan 정렬 키가 $1을 행마다 변환하지 않는지: 수정 전 실패, 수정 후 통과
    • enable_sort=off일 때 HNSW 정렬 Index Scan을 쓰는지: 수정 전 실패, 수정 후 통과
  • LexicalSearchRepositoryIntegrationTest 12/12 통과
  • ./backend/gradlew -p backend test (develop a24d3e8 + 이번 수정, 격리 DB·Valkey): 1,511 tests, 0 failures, 0 errors, 5 skipped

변경 파일

  • VectorSearchRepository.java: SQL 3곳 + Javadoc. 파라미터 타입·서비스 코드·스키마·설정은 그대로입니다.
  • VectorSearchRepositoryGenericPlanIntegrationTest.java: 신규
  • 설계 문서, 결과 문서, docs/test-results/evidence/issue-549/(원시 데이터 gzip, 비교 CSV·표, 그래프 4개, psql 증빙, 재현 스크립트)

🛠️ 추후 리팩토링 및 고도화 계획

  • 하이브리드 검색은 수정 후에도 5만 청크·허용 100%에서 p50 1.3초입니다. 남은 시간은 단어 갈래(정규식) 조회 비용이라 별도로 측정·개선이 필요합니다.
  • 장기적으로는 질문 Vector를 vector 타입으로 Bind하는 방법도 검토할 수 있습니다(의존성 추가가 필요해 이번에는 하지 않음).

📸 스크린샷 (선택)

수정 전후 검색 지연 (Generic Plan, 로그 축)

수정 전후 검색 지연

개선율

Generic Plan 지연 개선율

수정 후 결과 (#545와 같은 형식, 권한 필터 결과는 그대로)

수정 후 허용 문서 비율별 최종 결과 수

수정 후 허용 100%에서 SQL 반환 후보 수

💬 리뷰 요구사항

  • 검색 SQL을 기능 동결(10/24)·기능테스트(10/28) 전에 바꾸는 변경입니다. 결과(청크·순서)는 같지만, 머지 시점을 함께 정하고 싶습니다.
  • 수 ms 이하 조합(허용 0.1% 등)의 개선율은 측정 흔들림이 큽니다. 판단은 100%·10% 조합과 같은 Index 비교를 근거로 했습니다.

같은 SQL이 반복돼 Generic Plan이 쓰이면 CAST(:queryVector AS vector)가
행마다 다시 실행돼 1024차원 문자열을 검색 대상 청크 수만큼 파싱했다.
스칼라 서브쿼리로 감싸 InitPlan으로 실행마다 한 번만 계산한다.

- findTopK의 거리 계산·ORDER BY, findLexicalCandidates의 거리 계산
- 파라미터 타입·서비스 코드·스키마·설정은 그대로
운영 @query SQL을 그대로 PREPARE해 Generic Plan을 강제하고 검증한다.
- 결과가 Custom Plan과 같은 청크·거리 순서인지
- 정렬 키가 $1을 행마다 변환하지 않고 InitPlan 결과를 쓰는지
- enable_sort=off일 때 HNSW 정렬 Index Scan을 여전히 쓰는지
원인(Generic Plan에서 행마다 CAST), 변경, 고려한 다른 방법, API 계약을 정리한다.
- 두 results.json을 조합별 개선율·결과 일치 표와 SVG 그래프로 비교
- 수정 전/후 SQL의 Custom/Generic Plan·auto 반복 실행 psql 스크립트
- 같은 HNSW Index에서 수정 전/후 결과·실행 시간 비교 psql 스크립트
#545와 같은 환경(같은 이미지 digest·데이터·Seed·질문·하네스)에서 측정했다.
- 5만 청크·topK 10 Generic Plan 벡터 검색 p50: 4,226.3 → 207.4ms (95.1%)
- 수정 후 Generic Plan이 Custom Plan과 같은 속도, 검색 결과 동일
- A/B 연달아 측정, 전체 192개 조합 재측정, psql 반복 실행·같은 Index 비교
@Gimini-3 Gimini-3 added the 🐞 Fix 버그 수정 label Oct 17, 2026
@Gimini-3 Gimini-3 self-assigned this Oct 17, 2026
@Gimini-3
Gimini-3 requested a review from kangcheolung October 17, 2026 15:18
@coderabbitai

coderabbitai Bot commented Oct 17, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration
  • Configuration used: Repository: DocGrid/docgrid/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 6eabebda-651a-4a75-951f-ff909bc0160a
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@Gimini-3
Gimini-3 merged commit a109950 into develop Oct 17, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🐞 Fix 버그 수정

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Fix] Generic Plan에서 질문 Vector를 행마다 변환해 벡터 검색이 느려지는 문제

1 participant