Skip to content

#350 (exploration) 한글 검색어가 조각으로 쪼개져 무관한 결과가 섞이는 문제 수정 - #353

Merged
chanyong1027 merged 1 commit into
devfrom
exploration/fix/#350-korean-search-tokenization
Aug 31, 2026
Merged

#350 (exploration) 한글 검색어가 조각으로 쪼개져 무관한 결과가 섞이는 문제 수정#353
chanyong1027 merged 1 commit into
devfrom
exploration/fix/#350-korean-search-tokenization

Conversation

@chanyong1027

Copy link
Copy Markdown
Collaborator

📌 작업 내용

배경

한글 아티스트명으로 검색하면 관계없는 앨범이 대량으로 딸려 나온다. 부분 입력으로는 아예 찾지 못한다.

검색어 현재 실제 관련 상품
르세라핌 986건 11건
혁오 1,284건 4건
r.e.m. 9,089건 102건
르세 (부분 입력) 3,253건 (1위 신중현) 11건

상위 몇 건은 정확해서 눈에 잘 띄지 않지만, 페이지를 넘기면 엉뚱한 앨범만 나온다.

원인 1 — 검색어 조각 사이에 조건이 없었다

검색어는 두 단계로 나뉜다. 먼저 코드가 공백으로 자르고, 그다음 검색 엔진의 한국어 분석기가 각 단어를 다시 자른다.

"the beatles" → 공백으로  the / beatles  → 코드가 "둘 다 있어야 통과"로 묶고 있었음
"르세라핌"     → 분석기가  르 / 세라핌     → 아무 조건이 없었음
"r.e.m."      → 분석기가  r / e / m       → 아무 조건이 없었음

바깥쪽에는 조건이 있는데 안쪽에는 없었다. 그래서 "르"나 "e" 하나만 들어간 상품이 전부 결과에 들어왔다. 한글은 분석기가 대부분의 이름을 쪼개기 때문에 특히 심하고, 영문도 마침표나 슬래시가 들어간 이름(R.E.M., AC/DC)은 같은 문제를 겪고 있었다.

원인 2 — 부분 입력을 받아줄 필드가 질의에 없었다

artistName·artistAliases는 분석기가 쪼갠 형태로 저장되어 「르세」가 「르세라핌」에 닿지 못한다. 반면 하위 필드 .surface는 표기를 통째로 보관한다.

artistAliases          "르세라핌" → ['르', '세라핌']
artistAliases.surface  "르세라핌" → ['르세라핌']

이 하위 필드는 이미 색인에 있고 가산점 판정에 쓰이고 있었지만, 앞부분 일치 질의는 걸려 있지 않았다.

변경 사항

  1. buildTypoQuery의 오타 절에 operator(Operator.And)를 지정해 분석기가 쪼갠 조각을 모두 만족해야 통과하게 했다
  2. buildQuery에 앞부분 일치 절을 추가했다. .surface 필드에 phrase_prefix로 맞춰보며, 가중치는 PREFIX_BOOST = 0.7f
  3. 질의 구조를 고정하는 단위 테스트 3건을 추가하고, 버킷 수가 바뀐 기존 테스트 2건을 갱신했다

주요 결정과 트레이드오프

가중치를 0.7로 둔 이유. 이름을 끝까지 친 사람의 결과가 항상 위여야 하므로 정확 매칭(100)보다 훨씬 낮게 뒀다. 다만 앞부분이 겹치는 아티스트는 오타로 걸린 문서보다는 쓸모 있다고 보아 오타 절(0.5)보다는 높게 뒀다.

한글 오타 보정은 이번에 다루지 않았다. 분석기가 「라흐마니노푸」를 「라흐·마니·노·푸」로 쪼개 놓으면 원래 단어와의 거리를 잴 수 없어, 지금도 검색하면 1위가 남진으로 나온다. 제대로 하려면 한글을 자모 단위로 풀어 색인하는 별도 필드가 필요하고 매핑 변경과 재색인이 따라온다. 이 PR은 질의만 고친다.

영향 범위

로컬 색인(43.5만 건)에 직접 질의를 던져 실측했다.

검색어 현재 수정 후
르세라핌 986건 11건
혁오 1,284건 4건
r.e.m. 9,089건 102건 (1위 R.E.M. 유지)
르세 3,253건 (1위 신중현) 11건 (1위 Le Sserafim)
라흐마 958건 (1위 라흐마니노프)
비틀즈 587건 587건
the beatles 668건 668건
rachmaninof (오타) 1,069건 1,069건
비틀즈 abbey road 6건 6건

영문 오타 보정은 그대로 동작한다. 영문은 한 단어가 조각 하나로 남아 "하나만 만족"과 "전부 만족"이 같은 조건이 되기 때문이다.

의도한 부작용이 두 가지 있다.

앞부분이 겹치는 아티스트가 하위에 붙는다. 「아바」가 217건에서 235건이 되는데, 늘어난 18건은 클라우디오 아바도다. 가중치가 낮아 ABBA 뒤에 위치한다.

ac dk처럼 여러 단어 중 짧은 단어에 오타가 난 경우는 0건이 된다(현재 154건, 1위 Dead Kennedys). 네 글자 미만에는 오타 보정이 적용되지 않기 때문인데, 원하는 답이 나오던 경우가 아니라 손실로 보지 않았다.

🔗 관련 이슈

Closes #350

📂 영향받는 서비스

  • exploration-service
  • 그 외 — API 스펙 변경 없음. 검색 결과의 내용과 순위만 달라진다

✅ 체크리스트

  • 컴파일 통과 (:exploration-service:compileJava :compileTestJava)
  • exploration-service 테스트 182건 전부 통과
  • 전체 빌드 (./gradlew build) — 리뷰 전 확인 예정
  • ApiResponse 포맷 준수 (응답 형식 변경 없음)
  • 컨벤션 준수
  • API 문서화 반영 불필요 (스펙 변경 없음)

🧪 테스트 방법

단위 테스트는 질의의 구조만 확인한다. 실제 결과 건수와 순위는 Elasticsearch가 있어야 확인할 수 있다.

./gradlew :exploration-service:test --tests "*ProductSearchQueryTest*"

실제 동작은 검색 API로 확인한다. 아래 검색어에서 결과가 크게 줄고 1위가 유지되는지 보면 된다.

르세라핌   986건 → 11건
혁오     1,284건 → 4건
르세     1위가 신중현 → Le Sserafim
비틀즈    587건 그대로

💬 리뷰 요청 사항

1. 앞부분 일치의 가중치 0.7이 적절한지 봐주세요.

ProductSearchRepositoryImplPREFIX_BOOST입니다. 이 값이 1을 넘으면 앞부분만 겹치는 아티스트가 정확히 맞는 결과를 밀어낼 수 있고, 0.5 아래로 내려가면 오타로 걸린 문서보다 뒤로 밀립니다. 그 사이에서 0.7을 골랐는데, 「아바」 검색 시 클라우디오 아바도가 ABBA 뒤에 붙는 정도가 적당한지 판단이 필요합니다.

2. 한 글자 검색을 허용해도 될지 봐주세요.

지금은 글자 수 제한이 없어서 「르」 한 글자만 쳐도 645건, 「비」는 3,515건이 나옵니다(응답은 6~14ms라 성능 문제는 없습니다). 자동완성처럼 쓰기에는 자연스럽지만, 검색창에 입력하는 중간 상태에서 화면이 계속 바뀌는 것이 부담스러우면 두 글자 이상일 때만 적용하도록 조건을 넣을 수 있습니다.

3. ac dk 사례를 손실로 봐야 할지 봐주세요.

여러 단어 중 짧은 단어에 오타가 나면 결과가 0건이 됩니다. 지금은 154건이 나오지만 1위가 Dead Kennedys라 원하는 답은 아닙니다. 저는 "엉뚱한 154건보다 0건이 낫다"고 판단했는데, 검색 결과가 아예 없는 화면이 사용자에게 더 나쁜 경험이라는 시각도 가능합니다.

- 오타 절에 operator를 and로 지정: 검색어는 공백으로 한 번, 분석기가 한 번 더 나뉘는데
  안쪽에 조건이 없어 조각 하나만 걸린 상품까지 결과에 들어왔다 (르세라핌 986건 → 11건)
- 표기가 통째로 남은 surface 필드에 앞부분 일치 절 추가: 이름을 끝까지 치지 않아도 찾게 한다
  (「르세」로 르세라핌 검색 가능, 기존에는 1위가 신중현)
- 질의 구조를 고정하는 단위 테스트 3건 추가
@chanyong1027 chanyong1027 self-assigned this Aug 31, 2026
@chanyong1027
chanyong1027 merged commit 86becec into dev Aug 31, 2026
1 check passed
@chanyong1027

Copy link
Copy Markdown
Collaborator Author

골든셋 측정 결과 (머지 후 추가)

같은 색인·같은 데이터에서 dev와 이 브랜치를 각각 재서 질의 수정만의 효과를 분리했습니다. 문항 431개, 로컬 ES 43.5만 건 기준입니다.

유형 dev 수정 후 차이
A. 정확검색 0.969 0.969 0
B. 한글경유 0.985 0.985 0
C. 오타변형 0.787 0.787 0
D. 번호검색 1.000 1.000 0
E. 탐색형(nDCG) 0.049 0.000 -0.049
F. 한영혼합 0.949 0.949 0

다섯 유형이 소수점 셋째 자리까지 같습니다. 골든셋이 재는 범위에서 기존 검색 품질은 그대로입니다.

개선 — 점수표에 안 잡히는 항목

G 유형은 카탈로그에 없는 인물을 검색해 결과가 없어야 정답인 문항입니다. 여기서 오검색이 사라졌습니다.

검색어 dev 수정 후
셸백 162건 (「백년」, 「천칠백삼십」 …) 0건
휴닝카이 310건 (Kai Winding …) 0건

조각 하나만 겹쳐서 걸리던 문제가 그대로 드러난 사례입니다.

유일하게 떨어진 E 유형

"1980년대 클래식"처럼 장르와 연대를 자연어로 치는 문항 50개입니다. 다만 검색 질의가 보는 필드는 제목과 아티스트명뿐이고 장르 필드는 들어 있지 않아, 원래 지원하지 않는 기능입니다.

dev에서 점수가 났던 것은 이런 경우였습니다.

1950년대 재즈  → Modern Jazz Quartet - Germany 1956 & 1958
1960년대 블루스 → Moody Blues - Live At The BBC 1967-1

장르를 이해해서 찾은 것이 아니라 「재즈」나 「1950」이 제목 어딘가에 우연히 걸린 결과이고, 조건이 좁혀지면서 사라졌습니다. 탐색형 검색을 제대로 지원하려면 장르 필드를 질의에 넣는 별도 작업이 필요합니다.

리포트

  • docs/search/runs/2026-08-31-1600-dev-same-data.md (dev 기준)
  • docs/search/runs/2026-08-31-1548-and-prefix.md (수정 후)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

(exploration) 한글 검색어가 형태소 조각으로 쪼개져 관계없는 결과가 대량 노출

1 participant