본문으로 건너뛰기
김신건의 로그

[Search] ES 한글 인덱싱: nori, mecab, 형태소 분석

· 수정 · 📖 약 2분 · 678자/단어 #elasticsearch #korean #nori #mecab #analyzer #tokenizer
한글 인덱싱, nori, nori_tokenizer, mecab-ko, mecab-ko-analyzer, arirang, 한국어 분석기, 한글 형태소 분석, Korean analyzer

정의

한국어교착어 + 띄어쓰기 모호 + 형태소 변화 때문에 공백 단순 분리 로는 검색 불가. 형태소 분석 (morphological analysis) 필수.

한글이 어려운 이유

flowchart LR
    Word["사과를 먹는다"] --> Naive["공백 분리:<br/>[사과를, 먹는다]"]
    Word --> Morph["형태소 분석:<br/>[사과(N), 를(J), 먹(V), 는(E), 다(E)]"]
    Naive --> Bad["'사과' 검색 → 안 매칭"]
    Morph --> Good["'사과' 검색 → 매칭"]
영어한국어
띄어쓰기 = 단어 경계모호 (붙여쓰기 허용)
어형 변화 적음 (s, ed, ing)극심 (조사, 어미, 어간)
stemmer 로 충분형태소 분석기 필요

한국어 형태소 분석 직관

Aho-Corasick (다중 패턴 매칭) 의 동작 직관. 한국어 형태소 분석도 사전 + 트라이 기반의 모든 가능한 경계 후보 동시 매칭 + 최적 분리 선택. 핵심 아이디어가 동일.

사전 (dictionary) 의 trie 구조. 형태소 분석기의 내부 사전 도 trie 또는 FST.

주요 분석기 비교

분석기출시사전특징
nori2018 (Elastic 공식)mecab-ko-dic공식 + 안정 + Lucene 기본 통합
mecab-ko-analyzer2014mecab-ko-dic옛 표준. 별도 plugin 필요
arirang2010자체 사전옛, 현재 거의 안 씀
eunjeon (은전한닢)2014mecab-ko-dicmecab-ko 기반
OpenKoreanText (twitter-korean)2014자체트위터 특화

IMPORTANT

2026 시점 권장: nori. 공식 + 활발한 유지보수 + Lucene 통합 + 한국어 search 의 de facto.

nori 사용

# 플러그인 설치
bin/elasticsearch-plugin install analysis-nori
PUT /products
{
  "settings": {
    "analysis": {
      "tokenizer": {
        "nori_user_dict": {
          "type": "nori_tokenizer",
          "decompound_mode": "mixed",
          "user_dictionary": "userdict_ko.txt"
        }
      },
      "filter": {
        "nori_pos_remove": {
          "type": "nori_part_of_speech",
          "stoptags": ["E", "IC", "J", "MAG", "MAJ", "MM", "SP", "SSC", "SSO", "SC", "SE", "XPN", "XSA", "XSN", "XSV", "UNA", "NA", "VSV"]
        }
      },
      "analyzer": {
        "korean": {
          "type": "custom",
          "tokenizer": "nori_user_dict",
          "filter": ["lowercase", "nori_readingform", "nori_pos_remove"]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "title": { "type": "text", "analyzer": "korean" }
    }
  }
}

decompound_mode (복합어 분해)

"백화점" → 백화 + 점 ?
모드동작결과
none분해 안 함[백화점]
discard분해 + 원본 버림[백화, 점]
mixed (권장)분해 + 원본 유지[백화점, 백화, 점]

TIP

mixed 가 권장. 원본도 검색 + 분해된 단어도 검색 가능. recall 향상.

분석 테스트

POST /_analyze
{
  "tokenizer": "nori_tokenizer",
  "text": "백화점에서 아이폰 15 프로를 샀어요"
}
{
  "tokens": [
    { "token": "백화점", "start_offset": 0, "end_offset": 3, "type": "word", "position": 0 },
    { "token": "에서",    "start_offset": 3, "end_offset": 5, "type": "word", "position": 1 },
    { "token": "아이폰",  "start_offset": 6, "end_offset": 9, "type": "word", "position": 2 },
    { "token": "15",     "start_offset": 10, "end_offset": 12, "type": "word", "position": 3 },
    { "token": "프로",    "start_offset": 13, "end_offset": 15, "type": "word", "position": 4 },
    { "token": "를",      "start_offset": 15, "end_offset": 16, "type": "word", "position": 5 },
    { "token": "샀",      "start_offset": 17, "end_offset": 18, "type": "word", "position": 6 },
    { "token": "어요",    "start_offset": 18, "end_offset": 20, "type": "word", "position": 7 }
  ]
}

품사 필터 (POS filter)

nori_part_of_speech불필요 품사 제거:

태그의미보통 제외?
J조사 (은, 는, 이, 가)
E어미 (다, 어요, 았)
MAG일반 부사
IC감탄사
NNG일반 명사유지
NNP고유 명사유지
VV동사유지 (어간)
VA형용사유지
XR어근유지

User Dictionary (사용자 사전)

userdict_ko.txt:

아이폰
삼성갤럭시
대한항공
미스터트롯
삼성전자

신조어 / 고유명사 / 브랜드 등 분리되어선 안 될 단어 등록.

한자 / 영문 / 발음 변환

"filter": {
  "korean_readingform": { "type": "nori_readingform" }
}
  • 한자 → 한글 발음 변환 (漢字한자).
  • 검색 시 한자 입력 OK + 한글로 매칭.

ngram 기반 한글 검색

복합어 / 부분 매칭이 필요하면 ngram 보조:

"filter": {
  "edge_ngram_filter": {
    "type": "edge_ngram",
    "min_gram": 1,
    "max_gram": 10
  }
}
  • 자동완성 (autocomplete) 의 표준.
  • samsung[s, sa, sam, samp, sams, samsu, samsun, samsung].

흔한 함정

WARNING

  1. 공백 분리만 사용 = 조사 가 붙은 단어 검색 실패. nori 필수.
  2. POS filter 안 함 = J/E 가 토큰 폭증 → 인덱스 크기 + 검색 잡음.
  3. decompound_mode none = 백화점만 매칭, 백화 안 됨. mixed 권장.
  4. User dictionary 갱신 후 재인덱싱 안 함 = 옛 인덱스는 그대로 옛 분석. 신조어 반영 안 됨.

관련 위키

이 글의 용어 (5개)
[Search] ElasticSearch 기본 원리: Inverted Index, Segment, Lucenesearch
정의 ES 의 모든 동작 은 Lucene 의 inverted index 위에 얹혀있다. 문서 단위 저장 + term 단위 검색 의 분리. Inverted Index (역색인) 위…
[Search] ES Indexing: bulk, refresh, ingest pipelinesearch
정의 Indexing = JSON 문서를 역색인에 등록 하는 행위. Lucene segment 빌드 + refresh + flush 의 흐름. 단일 인덱싱 | 동작 | 결과 | …
[Search] ES Mapping: field type, dynamic, multi-fieldsearch
정의 Mapping = ES 의 schema. 각 필드의 type + analyzer + indexing 옵션. 대부분 immutable (재인덱싱 필요). Field Type …
트라이 (Trie, 접두사 트리)algorithm
정의 트라이 (Trie) 는 Edward Fredkin 이 1960년 제안한 문자열 집합을 저장하는 트리 자료구조. 각 노드는 문자 하나, 간선은 다음 문자로의 전이. 루트에서 …
Aho-Corasick (아호-코라식)algorithm
정의 Aho-Corasick 은 Alfred Aho 와 Margaret Corasick 이 1975년 고안한 다중 패턴 문자열 매칭 알고리즘. Trie 에 KMP failure …

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기