📌 Description
현재 약 검색(GET /api/search/drugs)은 DrugDocument.itemName에 기본(standard) 애널라이저 + match_phrase_prefix 쿼리 하나만 사용한다. 다음 한계가 있다.
- 한글 형태소 분석 없음 → "타이레놀 500" vs "타이레놀500" 토큰화 불일치
- 초성 검색 불가 (
ㅌㅇㄹㄴ → 0건)
- 오타 불가 (
타이레올 → 0건)
match_phrase_prefix는 마지막 토큰만 prefix 취급 + 스코어링이 빈약 → 결과 순서가 사실상 무작위
- 제품명 중간 단어로 검색 불가 ("이알서방")
전제
무중단 재색인 인프라(#90, alias + POST /api/admin/drugs/reindex)가 머지되어, DrugDocument 매핑을 바꾼 뒤 재색인 API 한 번으로 다운타임 없이 반영할 수 있다. 이 이슈는 그 매핑/쿼리를 실제로 고도화한다.
🎯 목표
| 지표 |
현재(측정 필요) |
목표 |
| 검색 p95 latency |
? |
현재 수준 유지 |
| 정답률 (라벨링 20~30개, Recall@10) |
? |
≥ 0.9 |
| 초성 검색 |
❌ |
✅ (ㅌㅇㄹㄴ → 타이레놀 계열) |
| 오타 1글자 허용 |
❌ |
✅ (타이레올 → 타이레놀 계열) |
| 관련도 정렬 |
❌ |
✅ (완전일치 > 시작일치 > 부분일치) |
🛠️ 작업 범위
1. 인덱스 settings/mappings 재설계 (DrugDocument)
itemName을 멀티필드로:
itemName — nori 형태소 분석 (정확 매칭용)
itemName.ngram — edge_ngram (min 1, max 20) 자동완성용
itemName.chosung — 초성 필드 (색인 시 Java에서 자모 분해 후 주입)
number_of_replicas: 0 명시 (single-node → 현재 클러스터 yellow)
@Setting으로 커스텀 애널라이저 정의. DrugIndexManager.createTimestampedIndex()가 createSettings()/createMapping()으로 그대로 반영하므로 코드 변경 없이 재색인 API로 배포된다.
2. 초성 분해 유틸 (HangulChosungExtractor)
- "타이레놀정" → "ㅌㅇㄹㄴㅈ"
- 한글 유니코드 조합 분해 (0xAC00 기준 초성 인덱스)
- 비한글(숫자/영문)은 그대로 통과
DrugInfoConverter.toDocument에서 chosung 필드 채움
3. 검색 쿼리 교체 (DrugSearchRepository / DrugSearchQueryService)
match_phrase_prefix → bool.should 조합:
match itemName.ngram (자동완성 기본, boost 1)
match itemName + fuzziness: AUTO (정확 일치 + 오타, boost 3)
term itemName.chosung (입력이 전부 초성일 때만, boost 2)
입력이 "전부 초성인지" 판별해 chosung 절 on/off.
4. 관련도 정렬
function_score 또는 bool boost로 완전일치 > 시작일치 > 부분일치
DrugSearchQueryService.search가 SearchHit<DrugDocument>를 받도록 변경 → _score 보존 (현재는 버려짐)
- (선택)
DrugSearchResponse에 score 필드 — 튜닝/디버깅용, 최종 노출 여부는 논의
5. (선택) 동의어
- 성분명(아세트아미노펜) ↔ 대표 제품명 매핑 사전
- 범위 크면 별도 이슈로 분리
6. before/after 측정
- 라벨링 테스트셋(약명 20~30개 + 기대 결과) → Recall@10, MRR
- p95 latency
- 재색인 중 1초 간격 검색 → 실패 요청 0건 로그 (무중단 검증)
✅ To-do
📒 기타
- nori 플러그인: ES 8.x 기본 번들에
analysis-nori 포함 (별도 설치 불필요) — 확인
- 폴백(MySQL LIKE,
/api/drugs/search)은 이번 범위 아님 — 그대로 둠
- 커밋 분리: util(chosung) → document → converter → repository → service
📌 Description
현재 약 검색(
GET /api/search/drugs)은DrugDocument.itemName에 기본(standard) 애널라이저 +match_phrase_prefix쿼리 하나만 사용한다. 다음 한계가 있다.ㅌㅇㄹㄴ→ 0건)타이레올→ 0건)match_phrase_prefix는 마지막 토큰만 prefix 취급 + 스코어링이 빈약 → 결과 순서가 사실상 무작위전제
무중단 재색인 인프라(#90, alias +
POST /api/admin/drugs/reindex)가 머지되어,DrugDocument매핑을 바꾼 뒤 재색인 API 한 번으로 다운타임 없이 반영할 수 있다. 이 이슈는 그 매핑/쿼리를 실제로 고도화한다.🎯 목표
ㅌㅇㄹㄴ→ 타이레놀 계열)타이레올→ 타이레놀 계열)🛠️ 작업 범위
1. 인덱스 settings/mappings 재설계 (
DrugDocument)itemName을 멀티필드로:itemName— nori 형태소 분석 (정확 매칭용)itemName.ngram—edge_ngram(min 1, max 20) 자동완성용itemName.chosung— 초성 필드 (색인 시 Java에서 자모 분해 후 주입)number_of_replicas: 0명시 (single-node → 현재 클러스터 yellow)@Setting으로 커스텀 애널라이저 정의.DrugIndexManager.createTimestampedIndex()가createSettings()/createMapping()으로 그대로 반영하므로 코드 변경 없이 재색인 API로 배포된다.2. 초성 분해 유틸 (
HangulChosungExtractor)DrugInfoConverter.toDocument에서chosung필드 채움3. 검색 쿼리 교체 (
DrugSearchRepository/DrugSearchQueryService)match_phrase_prefix→bool.should조합:match itemName.ngram(자동완성 기본, boost 1)match itemName+fuzziness: AUTO(정확 일치 + 오타, boost 3)term itemName.chosung(입력이 전부 초성일 때만, boost 2)입력이 "전부 초성인지" 판별해 chosung 절 on/off.
4. 관련도 정렬
function_score또는boolboost로 완전일치 > 시작일치 > 부분일치DrugSearchQueryService.search가SearchHit<DrugDocument>를 받도록 변경 →_score보존 (현재는 버려짐)DrugSearchResponse에 score 필드 — 튜닝/디버깅용, 최종 노출 여부는 논의5. (선택) 동의어
6. before/after 측정
✅ To-do
HangulChosungExtractor+ 단위 테스트 (혼합 문자열, 자음/모음만, 빈 문자열)DrugDocument멀티필드 +@Setting반영DrugInfoConverter.toDocument— chosung 주입DrugSearchRepository쿼리 교체DrugSearchQueryService— 초성 입력 판별,SearchHit수신, score 처리📒 기타
analysis-nori포함 (별도 설치 불필요) — 확인/api/drugs/search)은 이번 범위 아님 — 그대로 둠