Back to Blog
ElasticsearchAnalyzerTokenizerNori한국어 검색Custom Analyzer

0x05. Elasticsearch 한국어 분석과 커스텀 Analyzer

Elasticsearch의 텍스트 분석 파이프라인을 이해하고, 한국어 형태소 분석기 Nori를 활용한 커스텀 Analyzer를 구성한다.

Elasticsearch에서 text 타입 필드에 문서를 인덱싱하면, 원본 텍스트가 그대로 저장되는 것이 아니다. Analyzer라는 텍스트 처리 파이프라인을 거쳐 토큰으로 분해된 후 역색인에 저장된다. "어떤 Analyzer를 사용하느냐"가 곧 검색 품질을 결정한다.

영어는 공백과 어간 추출(Stemming)만으로도 합리적인 검색이 가능하지만, 한국어는 교착어(조사, 어미가 붙는 언어)이기 때문에 단순 공백 분리로는 제대로 된 검색이 불가능하다. 이 글에서는 Analyzer의 구조를 이해하고, 한국어 검색을 위한 Nori 분석기 설정을 다룬다.


Analyzer의 구조

Analyzer는 세 가지 구성 요소로 이루어진다.

입력 텍스트
    
[Character Filter]  문자 단위 전처리
    
[Tokenizer]  토큰으로 분리
    
[Token Filter]  토큰 후처리
    
토큰 목록 (역색인에 저장)

Character Filter

텍스트가 토큰화되기 전에 문자 수준에서 변환을 수행한다. 0개 이상 적용 가능하다.

  • html_strip: HTML 태그를 제거한다. <b>검색</b>검색
  • mapping: 문자를 다른 문자로 치환한다. &and
  • pattern_replace: 정규식으로 문자를 치환한다

Tokenizer

텍스트를 토큰(term) 으로 분리하는 핵심 단계이다. Analyzer당 정확히 1개만 지정한다.

Tokenizer동작예시 입력 → 출력
standard유니코드 단어 경계로 분리"Hello World" → [Hello, World]
whitespace공백 기준으로 분리"New York" → [New, York]
keyword분리하지 않음 (전체가 하나의 토큰)"New York" → [New York]
ngramN-gram으로 분리"abc" → [a, ab, abc, b, bc, c]
nori_tokenizer한국어 형태소 분석"한국어를 분석한다" → [한국어, 를, 분석, 한다]

Token Filter

토큰화된 결과에 후처리를 적용한다. 0개 이상, 순서대로 적용된다.

Token Filter동작
lowercase소문자 변환
stop불용어(a, the, is 등) 제거
stemmer어간 추출 (running → run)
synonym동의어 처리 (NY → New York)
nori_part_of_speech한국어 품사 기반 필터링

기본 제공 Analyzer

Elasticsearch는 여러 Built-in Analyzer를 제공한다.

Standard Analyzer (기본값)

"The Quick Brown Fox"
   [the, quick, brown, fox]

Standard Tokenizer + Lowercase Token Filter. 영어에서 가장 무난한 선택이지만, 한국어에서는 부적합하다.

"한국어 검색 엔진"
   [한국어, 검색, 엔진]  (공백 기준 분리만 수행)

"한국어를"로 검색하면 "한국어"와 매칭되지 않는다. 조사 "를"이 붙어있기 때문이다.

분석 결과 확인: _analyze API

Analyzer의 동작을 직접 확인할 수 있다.

POST _analyze
{
  "analyzer": "standard",
  "text": "Elasticsearch는 강력한 검색 엔진이다"
}

응답으로 각 토큰의 텍스트, 위치, 오프셋 정보가 반환된다. 커스텀 Analyzer를 설계할 때 반드시 이 API로 검증해야 한다.


Nori: 한국어 형태소 분석기

Nori는 Elasticsearch에 공식 포함된 한국어 형태소 분석 플러그인이다. 은전한닢(MeCab-ko) 사전을 기반으로 한다.

설치

# Elasticsearch 플러그인으로 설치
bin/elasticsearch-plugin install analysis-nori

기본 사용

PUT /korean-index
{
  "settings": {
    "analysis": {
      "analyzer": {
        "korean": {
          "type": "custom",
          "tokenizer": "nori_tokenizer"
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "title": {
        "type": "text",
        "analyzer": "korean"
      }
    }
  }
}

분석 결과를 확인해 보자.

POST /korean-index/_analyze
{
  "analyzer": "korean",
  "text": "한국어를 분석한다"
}
// 결과: [한국어, 를, 분석, 한다]

"한국어를"이 "한국어" + "를"로 분리되었다. 이제 "한국어"로 검색해도 "한국어를 분석한다"가 매칭된다.

nori_tokenizer 옵션

"tokenizer": {
  "my_nori": {
    "type": "nori_tokenizer",
    "decompound_mode": "mixed",
    "discard_punctuation": true,
    "user_dictionary": "userdict_ko.txt"
  }
}

decompound_mode: 복합어 처리 방식을 결정한다.

모드"삼성전자" 분석 결과
none[삼성전자]
discard[삼성, 전자]
mixed[삼성전자, 삼성, 전자]

mixed가 가장 유연하다. 복합어 전체로도, 분리된 단어로도 검색할 수 있다.

user_dictionary: 기본 사전에 없는 단어를 추가한다. 브랜드명, 전문 용어 등을 등록할 때 사용한다.

# config/userdict_ko.txt
삼성전자
카카오페이
쿠팡로켓배송

nori_part_of_speech: 품사 기반 필터링

조사, 어미 등 검색에 불필요한 품사를 제거한다.

"filter": {
  "my_pos_filter": {
    "type": "nori_part_of_speech",
    "stoptags": [
      "E",   // 어미
      "J",   // 조사
      "IC",  // 감탄사
      "MAJ", // 접속 부사
      "SP",  // 문장 부호
      "SSC", // 닫는 괄호
      "SSO", // 여는 괄호
      "SC",  // 구분자
      "SE",  // 줄임표
      "XSN", // 명사 접미사
      "XSV", // 동사 접미사
      "UNA", // 알 수 없는
      "NA",  // 분석 불능
      "VSV"  // 긍정 지정사
    ]
  }
}

"한국어를 분석한다"에 이 필터를 적용하면 [한국어, 분석]만 남는다. 조사 "를"과 어미 "한다"가 제거된다.


실전 커스텀 Analyzer 구성

한국어 블로그 검색용 Analyzer

PUT /blog-index
{
  "settings": {
    "analysis": {
      "tokenizer": {
        "nori_mixed": {
          "type": "nori_tokenizer",
          "decompound_mode": "mixed"
        }
      },
      "filter": {
        "nori_pos": {
          "type": "nori_part_of_speech",
          "stoptags": ["E", "J", "IC", "MAJ", "SP", "SSC", "SSO", "SC", "SE"]
        },
        "nori_readingform": {
          "type": "nori_readingform"
        }
      },
      "analyzer": {
        "korean_search": {
          "type": "custom",
          "tokenizer": "nori_mixed",
          "filter": [
            "nori_readingform",
            "lowercase",
            "nori_pos",
            "nori_number"
          ]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "title": {
        "type": "text",
        "analyzer": "korean_search",
        "fields": {
          "keyword": { "type": "keyword" }
        }
      },
      "content": {
        "type": "text",
        "analyzer": "korean_search"
      },
      "tags": {
        "type": "keyword"
      }
    }
  }
}

nori_readingform은 한자를 한글 독음으로 변환한다. "東京" → "동경".

검색어 자동완성용 Analyzer

자동완성에는 Edge N-gram 방식이 효과적이다. 입력 도중의 부분 문자열로도 검색되어야 하기 때문이다.

"analyzer": {
  "autocomplete_index": {
    "type": "custom",
    "tokenizer": "nori_mixed",
    "filter": ["lowercase", "nori_pos", "edge_ngram_filter"]
  },
  "autocomplete_search": {
    "type": "custom",
    "tokenizer": "nori_mixed",
    "filter": ["lowercase", "nori_pos"]
  }
},
"filter": {
  "edge_ngram_filter": {
    "type": "edge_ngram",
    "min_gram": 1,
    "max_gram": 10
  }
}

인덱싱 시에는 Edge N-gram을 적용하고(autocomplete_index), 검색 시에는 적용하지 않는다(autocomplete_search). 이렇게 인덱스 Analyzer와 검색 Analyzer를 분리하는 것이 자동완성의 핵심 패턴이다.

"title": {
  "type": "text",
  "analyzer": "autocomplete_index",
  "search_analyzer": "autocomplete_search"
}

동의어(Synonym) 처리

검색에서 동의어 처리는 사용자 경험에 큰 영향을 미친다.

"filter": {
  "synonym_filter": {
    "type": "synonym",
    "synonyms": [
      "ES, Elasticsearch, 엘라스틱서치",
      "DB, Database, 데이터베이스",
      "K8s, Kubernetes, 쿠버네티스"
    ]
  }
}

또는 파일로 관리할 수 있다.

"filter": {
  "synonym_filter": {
    "type": "synonym",
    "synonyms_path": "analysis/synonyms.txt"
  }
}

동의어 필터는 보통 Token Filter 체인의 마지막에 배치한다. 형태소 분석과 소문자 변환이 완료된 토큰에 동의어를 매칭하는 것이 정확도가 높다.


정리

  • Analyzer는 Character Filter → Tokenizer → Token Filter의 파이프라인이다
  • 한국어 검색에는 Nori 플러그인이 필수이며, decompound_mode: mixed와 품사 필터를 함께 사용한다
  • 인덱스/검색 Analyzer 분리: 자동완성처럼 인덱싱과 검색에 다른 분석이 필요한 경우 search_analyzer를 별도 지정한다
  • 동의어 처리로 사용자가 다양한 표현으로 검색해도 원하는 결과를 찾을 수 있게 한다
  • _analyze API로 항상 분석 결과를 검증하고, 기대한 대로 토큰이 생성되는지 확인한다