| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | ||||
| 4 | 5 | 6 | 7 | 8 | 9 | 10 |
| 11 | 12 | 13 | 14 | 15 | 16 | 17 |
| 18 | 19 | 20 | 21 | 22 | 23 | 24 |
| 25 | 26 | 27 | 28 | 29 | 30 | 31 |
- 1차원 DP
- 2차원 dp
- 99클럽
- @BeforeAll
- @BeforeEach
- @Builder
- @Entity
- @GeneratedValue
- @GenericGenerator
- @NoargsConstructor
- @Query
- @Table
- @Transactional
- Actions
- Amazon EFS
- amazon fsx
- Android Studio
- ANSI SQL
- api gateway 설계
- api gateway 필터
- ApplicationEvent
- argocd
- assertThat
- async/await
- AVG
- AWS
- aws autoscaling
- aws eks
- aws iam role
- AWS KMS
- Today
- Total
기록
[Kafka × Elasticsearch 기반 상품 검색 시스템] 5. Nori 분석기와 검색 품질 본문
한국어 검색의 가장 큰 특징은 “단어의 경계가 명확하지 않다”는 점이다. 영어는 공백으로 단어가 구분되지만, 한국어는 띄어쓰기 오류나 복합명사 때문에 단순 문자열 비교로는 정확한 검색이 어렵다. 예를 들어 “무선마우스”, “무선 마우스”, “무선형 마우스”는 모두 사용자가 같은 제품을 찾으려는 의도지만, 문자열 기준으로는 완전히 다른 단어다. 이런 문제를 해결하기 위해 Elasticsearch에서는 Nori 분석기를 사용한다. Nori는 한국어 형태소 분석기(morphological analyzer)로, 문장을 의미 단위로 분해하고 색인과 검색을 돕는다.
1. Nori 분석기의 기본 원리
Elasticsearch는 데이터를 저장하기 전에 “토큰화(tokenization)” 과정을 거친다. 이 과정에서 텍스트를 토큰(token) 이라는 단어 단위 조각으로 나누어 인덱스에 저장한다. 검색 시에도 입력 키워드를 같은 방식으로 토큰화하여, 저장된 토큰과 비교한다. 즉, 색인 시점과 검색 시점의 분석(analyze) 과정이 동일하게 일어나야 정확히 매칭된다.
예를 들어 다음과 같은 상품명을 보자.
"무선마우스 블루투스 연결"
Nori 분석기는 이를 다음과 같이 분리한다.
["무선", "마우스", "블루투스", "연결"]
따라서 사용자가 “무선 마우스” 또는 “블루투스 마우스”를 검색해도 동일 문서를 찾을 수 있다. 이것이 단순 공백 기반 토큰화보다 훨씬 정확한 이유다.
2. Nori 분석기 설정 구조
Nori 분석기는 Elasticsearch의 인덱스 설정(settings) 안에 정의된다. 아래는 본 프로젝트(products-settings.json)에서 사용한 기본 설정이다.
{
"analysis": {
"tokenizer": {
"nori_tokenizer_custom": {
"type": "nori_tokenizer",
"decompound_mode": "mixed"
}
},
"analyzer": {
"nori_korean": {
"type": "custom",
"tokenizer": "nori_tokenizer_custom",
"filter": ["lowercase"]
}
}
}
}
- tokenizer: 텍스트를 어떻게 쪼갤지를 결정한다.
- decompound_mode: 복합명사를 처리하는 방식(none, discard, mixed 중 선택).
- filter: 토큰에 추가 가공을 적용한다(예: 소문자 변환, 불용어 제거).
- analyzer: tokenizer와 filter를 조합한 하나의 분석 단위다.
이 구성은 단순하지만 한국어 검색 품질을 좌우한다.
3. 복합명사 처리 모드 이해하기
Nori의 핵심은 복합명사 처리(decompound_mode) 방식이다. 한국어는 두 단어 이상이 결합된 명사가 많기 때문에, 어떻게 분해할지를 정하는 것이 중요하다.
| 모드 | 설명 | 예시 ("무선마우스") |
| none | 분해하지 않음 | ["무선마우스"] |
| discard | 완성형만 버리고, 분해된 토큰만 저장 | ["무선", "마우스"] |
| mixed | 둘 다 저장 | ["무선마우스", "무선", "마우스"] |
추천 모드: mixed
실제 상품명에는 붙여 쓴 경우가 많고, 사용자는 띄어쓰거나 줄여 쓰기 때문에 두 형태를 모두 유지해야 검색 누락이 없다.
예를 들어 “무선마우스”로 저장된 문서도 “무선 마우스” 검색어로 찾을 수 있다.
4. lowercase 필터의 역할
"filter": ["lowercase"] 설정은 단순하지만 중요하다. 상품명에는 영어가 섞이는 경우가 많다. 예를 들어 "Logitech 무선 마우스"와 "logitech 무선 마우스"는 사실상 같은 상품이다. 소문자 변환 필터를 적용하면, 색인 시 "logitech"으로 통일되어 대소문자 구분 없이 검색이 가능하다. 이는 영문 브랜드명, 모델명, 약어 등이 섞인 데이터에서 검색 일관성을 높인다.
5. Nori가 검색 품질에 미치는 실제 효과
다음은 동일한 데이터를 일반 analyzer와 Nori analyzer로 검색했을 때의 차이이다.
| 검색어 | 일반 analyzer 결과 | Nori analyzer 결과 |
| 무선 마우스 | “무선마우스”만 색인된 문서 제외 | “무선마우스”, “무선 마우스” 모두 매칭 |
| 블루투스 마우스 | 일치 단어 없음 | “블루투스 무선마우스” 매칭 |
| 키보드 | “키보드”만 매칭 | “기계식 키보드”, “게이밍키보드”까지 포함 |
즉, Nori를 사용하면 복합명사와 띄어쓰기 차이로 발생하는 검색 누락을 최소화할 수 있다. 특히 상품명처럼 영어·한글·숫자가 혼합된 데이터에서 큰 효과를 발휘한다.
6. Nori 분석 결과 직접 확인하기
Elasticsearch는 _analyze API를 통해 분석 결과를 직접 확인할 수 있다.
curl -X POST "http://localhost:9200/_analyze" \
-H "Content-Type: application/json" \
-d '{
"tokenizer": "nori_tokenizer",
"text": "무선마우스 블루투스 연결"
}'
결과 예시:
{
"tokens": [
{ "token": "무선", "start_offset": 0, "end_offset": 2 },
{ "token": "마우스", "start_offset": 2, "end_offset": 5 },
{ "token": "블루투스", "start_offset": 6, "end_offset": 10 },
{ "token": "연결", "start_offset": 11, "end_offset": 13 }
]
}
이렇게 분석 결과를 직접 확인하면, 현재 설정이 의도대로 동작하는지 검증할 수 있다. 특히 사전(custom dictionary)을 추가했을 때나 필터 구성을 바꾼 후 테스트하기 좋다.
7. 품질 향상을 위한 추가 튜닝 방법
Nori 분석기만으로도 기본 검색 품질은 높아지지만, 실무에서는 다음 세 가지를 함께 고려하면 더 좋은 결과를 얻을 수 있다.
(1) 사용자 검색 패턴 기반 사전 추가
Nori는 내장 사전을 사용하지만, 실무에서는 브랜드명이나 신조어를 추가해야 한다. 예를 들어 "갤럭시북", "맥북에어", "LG그램" 같은 단어를 별도로 정의하면 형태소가 잘못 분리되는 문제를 방지할 수 있다. Elasticsearch는 사용자 정의 사전을 지원하므로, nori_user_dictionary.txt를 만들어 추가하면 된다.
갤럭시북
맥북에어
그램
이 사전을 컨테이너 볼륨으로 연결하거나 플러그인 디렉터리에 넣고 재시작하면 적용된다.
(2) 동의어(Synonym) 필터
한국어와 영어를 병행 검색하려면 "마우스 ↔ mouse", "키보드 ↔ keyboard"처럼 동의어 필터를 추가한다. 이렇게 하면 사용자 입력이 영어든 한글이든 동일한 결과를 돌려준다.
(3) 퍼지(Fuzzy) 검색과 최소 일치율
fuzziness: "AUTO"를 설정하면 오탈자에 유연하게 대응한다. 또한 minimum_should_match: "70%"를 적용하면 너무 느슨한 결과를 걸러내, 검색 품질을 안정화할 수 있다.
'Web > Spring' 카테고리의 다른 글
| [Kafka × Elasticsearch 기반 상품 검색 시스템] 4. 상품 검색 API 설계 (0) | 2025.12.15 |
|---|---|
| [Kafka × Elasticsearch 기반 상품 검색 시스템] 3. 상품 색인 (0) | 2025.12.08 |
| [Kafka × Elasticsearch 기반 상품 검색 시스템] 2. 상품 등록 이벤트 발행 (0) | 2025.12.01 |
| [Kafka × Elasticsearch 기반 상품 검색 시스템] 1. 상품 검색 엔진 설계 (0) | 2025.11.24 |
| [Swagger] Swagger에서 JWT 인증 처리하기 (0) | 2025.10.26 |