Elasticsearch 통합

MeCab-Ko를 Elasticsearch의 형태소 분석기로 사용하는 방법을 소개합니다.

개요

Elasticsearch는 검색 엔진으로, 한국어 검색을 위해 형태소 분석기가 필요합니다. MeCab-Ko는 Elasticsearch 플러그인 형태로 통합할 수 있습니다.

설치

플러그인 설치

# Elasticsearch 플러그인 설치
bin/elasticsearch-plugin install \
  https://github.com/hephaex/elasticsearch-analysis-mecab-ko/releases/download/v8.11.0/elasticsearch-analysis-mecab-ko-8.11.0.zip

버전 호환성:

ElasticsearchPlugin Version
8.11.x8.11.0
8.10.x8.10.0
7.17.x7.17.0

수동 빌드

# 소스 다운로드
git clone https://github.com/hephaex/elasticsearch-analysis-mecab-ko.git
cd elasticsearch-analysis-mecab-ko

# 빌드
./gradlew clean build

# 설치
bin/elasticsearch-plugin install file:///path/to/plugin.zip

사전 설치

# 사전 디렉토리 생성
sudo mkdir -p /usr/share/elasticsearch/config/mecab-ko-dic

# 사전 다운로드 및 압축 해제
wget https://github.com/hephaex/mecab-ko-dic/releases/latest/download/mecab-ko-dic.tar.gz
tar xzf mecab-ko-dic.tar.gz -C /usr/share/elasticsearch/config/mecab-ko-dic

설정

Analyzer 정의

PUT /my_index
{
  "settings": {
    "analysis": {
      "tokenizer": {
        "mecab_ko_tokenizer": {
          "type": "mecab_ko",
          "dict_path": "/usr/share/elasticsearch/config/mecab-ko-dic",
          "user_dict_path": "/usr/share/elasticsearch/config/user-dic.csv"
        }
      },
      "analyzer": {
        "mecab_analyzer": {
          "type": "custom",
          "tokenizer": "mecab_ko_tokenizer"
        }
      }
    }
  }
}

Tokenizer 옵션

{
  "tokenizer": {
    "mecab_ko_tokenizer": {
      "type": "mecab_ko",
      "dict_path": "/path/to/dict",
      "user_dict_path": "/path/to/user-dict.csv",
      "output_format": "mecab",
      "space_penalty": -1000,
      "compound_noun_min_length": 2,
      "decompound": true,
      "pos_filter": ["NNG", "NNP", "VV", "VA"],
      "max_unk_length": 24
    }
  }
}

옵션 설명:

옵션기본값설명
dict_path-사전 디렉토리 경로
user_dict_path-사용자 사전 파일 경로
output_formatmecab출력 포맷
space_penalty-1000띄어쓰기 패널티
compound_noun_min_length2복합명사 최소 길이
decompoundfalse복합명사 분해
pos_filter-품사 필터 (배열)
max_unk_length24미등록어 최대 길이

사용 예제

기본 분석

POST /my_index/_analyze
{
  "analyzer": "mecab_analyzer",
  "text": "형태소 분석을 시작합니다"
}

응답:

{
  "tokens": [
    {
      "token": "형태소",
      "start_offset": 0,
      "end_offset": 3,
      "type": "NNG",
      "position": 0
    },
    {
      "token": "분석",
      "start_offset": 4,
      "end_offset": 6,
      "type": "NNG",
      "position": 1
    },
    {
      "token": "을",
      "start_offset": 6,
      "end_offset": 7,
      "type": "JKO",
      "position": 2
    },
    {
      "token": "시작",
      "start_offset": 8,
      "end_offset": 10,
      "type": "NNG",
      "position": 3
    },
    {
      "token": "하",
      "start_offset": 10,
      "end_offset": 11,
      "type": "XSV",
      "position": 4
    },
    {
      "token": "ㅂ니다",
      "start_offset": 11,
      "end_offset": 14,
      "type": "EF",
      "position": 5
    }
  ]
}

품사 필터링

명사만 추출:

PUT /my_index
{
  "settings": {
    "analysis": {
      "tokenizer": {
        "mecab_ko_noun": {
          "type": "mecab_ko",
          "pos_filter": ["NNG", "NNP", "NNB"]
        }
      },
      "analyzer": {
        "noun_analyzer": {
          "type": "custom",
          "tokenizer": "mecab_ko_noun"
        }
      }
    }
  }
}

테스트:

POST /my_index/_analyze
{
  "analyzer": "noun_analyzer",
  "text": "저는 오늘 학교에 갑니다"
}

결과: ["오늘", "학교"]

복합명사 분해

PUT /my_index
{
  "settings": {
    "analysis": {
      "tokenizer": {
        "mecab_ko_decompound": {
          "type": "mecab_ko",
          "decompound": true,
          "compound_noun_min_length": 2
        }
      },
      "analyzer": {
        "decompound_analyzer": {
          "type": "custom",
          "tokenizer": "mecab_ko_decompound"
        }
      }
    }
  }
}

테스트:

POST /my_index/_analyze
{
  "analyzer": "decompound_analyzer",
  "text": "한국어형태소분석기"
}

결과: ["한국어", "한국", "어", "형태소", "형태", "소", "분석기", "분석", "기"]

인덱싱 및 검색

인덱스 생성

PUT /documents
{
  "settings": {
    "analysis": {
      "tokenizer": {
        "mecab_ko_tokenizer": {
          "type": "mecab_ko"
        }
      },
      "analyzer": {
        "korean_analyzer": {
          "type": "custom",
          "tokenizer": "mecab_ko_tokenizer",
          "filter": ["lowercase"]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "title": {
        "type": "text",
        "analyzer": "korean_analyzer"
      },
      "content": {
        "type": "text",
        "analyzer": "korean_analyzer"
      }
    }
  }
}

문서 인덱싱

POST /documents/_doc/1
{
  "title": "Elasticsearch 한국어 검색",
  "content": "MeCab-Ko를 사용한 형태소 분석 예제입니다."
}

POST /documents/_doc/2
{
  "title": "형태소 분석기 비교",
  "content": "여러 한국어 형태소 분석기를 비교합니다."
}

검색

GET /documents/_search
{
  "query": {
    "match": {
      "content": "형태소 분석"
    }
  }
}

하이라이팅

GET /documents/_search
{
  "query": {
    "match": {
      "content": "형태소"
    }
  },
  "highlight": {
    "fields": {
      "content": {}
    }
  }
}

고급 설정

멀티 필드

다양한 분석기를 동시에 사용:

PUT /multi_field_index
{
  "mappings": {
    "properties": {
      "title": {
        "type": "text",
        "analyzer": "korean_analyzer",
        "fields": {
          "ngram": {
            "type": "text",
            "analyzer": "ngram_analyzer"
          },
          "keyword": {
            "type": "keyword"
          }
        }
      }
    }
  }
}

동의어 처리

PUT /synonym_index
{
  "settings": {
    "analysis": {
      "filter": {
        "korean_synonym": {
          "type": "synonym",
          "synonyms": [
            "컴퓨터, PC, 피씨",
            "휴대폰, 핸드폰, 모바일"
          ]
        }
      },
      "analyzer": {
        "korean_synonym_analyzer": {
          "type": "custom",
          "tokenizer": "mecab_ko_tokenizer",
          "filter": ["lowercase", "korean_synonym"]
        }
      }
    }
  }
}

사용자 사전

user-dic.csv:

# 표면형,품사,비용,기본형
딥러닝,NNG,-1000,딥러닝
머신러닝,NNG,-1000,머신러닝
트랜스포머,NNG,-1000,트랜스포머
GPT,SL,-1000,GPT

설정:

{
  "tokenizer": {
    "mecab_ko_custom": {
      "type": "mecab_ko",
      "user_dict_path": "/usr/share/elasticsearch/config/user-dic.csv"
    }
  }
}

성능 최적화

인덱스 샤드 설정

PUT /optimized_index
{
  "settings": {
    "number_of_shards": 3,
    "number_of_replicas": 1,
    "refresh_interval": "30s",
    "analysis": {
      "tokenizer": {
        "mecab_ko_tokenizer": {
          "type": "mecab_ko"
        }
      }
    }
  }
}

필터 캐싱

GET /documents/_search
{
  "query": {
    "bool": {
      "filter": {
        "term": {
          "status": "published"
        }
      },
      "must": {
        "match": {
          "content": "검색어"
        }
      }
    }
  }
}

벌크 인덱싱

curl -X POST "localhost:9200/documents/_bulk" \
  -H "Content-Type: application/x-ndjson" \
  --data-binary @bulk_data.ndjson

bulk_data.ndjson:

{"index":{"_id":"1"}}
{"title":"제목1","content":"내용1"}
{"index":{"_id":"2"}}
{"title":"제목2","content":"내용2"}

모니터링

분석기 성능 확인

GET /_nodes/stats/indices/indexing

쿼리 성능 프로파일링

GET /documents/_search
{
  "profile": true,
  "query": {
    "match": {
      "content": "검색어"
    }
  }
}

실전 예제

블로그 검색

PUT /blog
{
  "settings": {
    "analysis": {
      "tokenizer": {
        "mecab_ko_tokenizer": {
          "type": "mecab_ko",
          "pos_filter": ["NNG", "NNP", "VV", "VA", "SL", "SH"]
        }
      },
      "analyzer": {
        "blog_analyzer": {
          "type": "custom",
          "tokenizer": "mecab_ko_tokenizer",
          "filter": ["lowercase", "trim"]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "title": {
        "type": "text",
        "analyzer": "blog_analyzer",
        "boost": 2.0
      },
      "content": {
        "type": "text",
        "analyzer": "blog_analyzer"
      },
      "tags": {
        "type": "keyword"
      },
      "published_date": {
        "type": "date"
      }
    }
  }
}

검색:

GET /blog/_search
{
  "query": {
    "bool": {
      "should": [
        {
          "match": {
            "title": {
              "query": "형태소 분석",
              "boost": 2
            }
          }
        },
        {
          "match": {
            "content": "형태소 분석"
          }
        }
      ],
      "filter": {
        "range": {
          "published_date": {
            "gte": "2024-01-01"
          }
        }
      }
    }
  },
  "highlight": {
    "fields": {
      "title": {},
      "content": {}
    }
  },
  "sort": [
    "_score",
    {"published_date": "desc"}
  ]
}

전자상거래 상품 검색

PUT /products
{
  "settings": {
    "analysis": {
      "tokenizer": {
        "mecab_ko_product": {
          "type": "mecab_ko",
          "decompound": true,
          "pos_filter": ["NNG", "NNP", "SL", "SN"]
        }
      },
      "analyzer": {
        "product_analyzer": {
          "type": "custom",
          "tokenizer": "mecab_ko_product",
          "filter": ["lowercase"]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "name": {
        "type": "text",
        "analyzer": "product_analyzer",
        "fields": {
          "keyword": {
            "type": "keyword"
          }
        }
      },
      "description": {
        "type": "text",
        "analyzer": "product_analyzer"
      },
      "price": {
        "type": "float"
      },
      "category": {
        "type": "keyword"
      }
    }
  }
}

문제 해결

플러그인 로딩 실패

# 로그 확인
tail -f /var/log/elasticsearch/elasticsearch.log

# 플러그인 목록 확인
bin/elasticsearch-plugin list

사전을 찾을 수 없음

# 사전 경로 확인
ls -la /usr/share/elasticsearch/config/mecab-ko-dic

# 권한 확인
chown -R elasticsearch:elasticsearch /usr/share/elasticsearch/config/mecab-ko-dic

성능 저하

# 분석기 캐시 통계 확인
GET /_nodes/stats/indices/query_cache

상세 문서

더 자세한 내용은 다음 문서를 참조하세요:

참고 자료