AWS 기술 블로그

Amazon OpenSearch Service, 한국어 분석을 위한 ‘노리(Nori)’ 플러그인 활용

Amazon OpenSearch Service는 애플리케이션 모니터링, 로그 분석, 웹 사이트 검색과 같은 사용 사례에서 비즈니스 또는 운영 데이터의 실시간 검색, 모니터링, 분석을 안전하게 제공합니다. 이 게시물에서는 한글 문서를 효과적으로 다루기 위해 노리 플러그인을 다루고 있습니다

개요

Amazon OpenSearch Service에서 유명한 오픈 소스 한국어 텍스트 분석기인 노리 플러그인을 지원합니다. 기존에 지원하던 은전한닢(Seunjeon) 플러그인과 더불어 노리를 활용하면 개발자가 한국 문서의 전문 검색을 쉽게 구현할 수 있습니다.

한글 텍스트 분석기를 사용하면 형태소를 분석하여 토큰화한 후 저장합니다. 이렇게 의미 단위의 토큰을 저장하면 한글 검색의 품질 향상을 얻을 수 있습니다. 자세한 예제는 아래에서 계속됩니다.

이와 함께, 중국어 분석을 위한 Pinyin 플러그인과 STConvert 플러그인 그리고 일본어 분석을 위한 Sudachi 플러그인도 추가됐습니다. 노리 플러그인은 OpenSearch 1.0 이상 버전을 실행하는 새 도메인과 기존 도메인에서 사용할 수 있습니다.

사전 설정

현재 노리 플러그인은 도메인 구성 시 자동으로 설치되지 않고 선택적(Optional)으로 사용할 수 있습니다. AWS 콘솔에서 Amazon OpenSearch Service로 이동합니다. 기존에 사용 중인 도메인을 선택하고 아래의 세부 탭 중에서 Packages를 선택합니다. 그리고 Associate package 버튼을 클릭합니다.

선택사항으로 표기된 여러 Package 중에서 Analysis-nori를 선택하고 Associate 버튼을 클릭합니다. 이때 블루/그린 배포가 트리거 되는 것에 유의하십시오.

배포가 완료되면 General information의 Domain status와 Packages의 analysis-nori 부분의 Association status가 Active 상태로 전환됩니다.

플러그인 확인

OpenSearch Dashboards의 Dev tools 또는 직접 API를 호출해서 확인할 수 있습니다. OpenSearch Dashboards에서 좌측 상단의 메뉴를 클릭한 뒤 Dev tools를 클릭합니다.

설정이 완료된 도메인에 아래 명령어로 노리 플러그인이 사용 가능한지 확인합니다.

GET _cat/plugins
  • 주의 : Dev Tools를 이용하여 API를 사용할 때는 호출할 코드 라인 내부에 커서가 위치한 상태에서 실행 버튼(삼각형 모양)을 클릭해야 합니다. 예를 들어, 이 화면에서는 1번 라인에 커서를 두었습니다. 만약, 다른 영역에 커서를 위치시킨 실행하면 “No request selected. Select a request by placing the cursor inside it.”의 오류메시지가 나타납니다.

응답 결과로 opensearch-analysis-nori가 포함된 것을 확인합니다.

분석기 비교 테스트

Amazon OpenSearch Service는 Analyze API를 이용해서 미리 토큰화된 결과를 확인할 수 있습니다.

노리 분석기를 이용한 테스트

요청

GET _analyze
{
  "analyzer": "nori",
  "text": "이제 OpenSearch에서도 노리를 사용할 수 있습니다."
}

결과

{
  "tokens": [
    {
      "token": "opensearch",
      "start_offset": 3,
      "end_offset": 13,
      "type": "word",
      "position": 1
    },
    {
      "token": "노리",
      "start_offset": 17,
      "end_offset": 19,
      "type": "word",
      "position": 4
    },
    {
      "token": "사용",
      "start_offset": 21,
      "end_offset": 23,
      "type": "word",
      "position": 6
    },
    {
      "token": "수",
      "start_offset": 25,
      "end_offset": 26,
      "type": "word",
      "position": 9
    },
    {
      "token": "있",
      "start_offset": 27,
      "end_offset": 28,
      "type": "word",
      "position": 10
    }
  ]
}

위의 결과와 같이 한글 문장을 분석하여 의미 있는 단위의 토큰을 반환합니다. 참고로, 분석기는 캐릭터 필터(Character filters), 토크나이저(Tokenizer), 토큰 필터(Token filters)로 구성되어 있습니다. 좀 더 자세한 내용은 이곳을 참고해 주십시오.

노리의 경우 nori_tokenizer(토크나이저), nori_part_of_speech token filter (제거할 품사 선택을 위한 토큰 필터), nori_readingform token filter(한자를 한글로 변경하는 토큰 필터),  lowercase token filter (소문자로 일괄 변경하는 토큰 필터)가 포함되어 있습니다.

위의 결과에서 사라진 글자가 있는 이유는 nori_part_of_speech token filter에서 기본값으로 제거하는 품사가 있기 때문입니다. 물론 이것도 사용자가 설정할 수 있습니다.

디폴트 (standard) 분석기를 이용한 테스트

요청

GET _analyze
{
  "analyzer": "standard", 
  "text": "이제 OpenSearch에서도 노리를 사용할 수 있습니다."
}
  • 참고: “analyzer”: “standard”, 를 제거하고 요청하더라도 기본적으로 text 필드 타입에는 standard가 적용됩니다.

결과

{
  "tokens": [
    {
      "token": "이제",
      "start_offset": 0,
      "end_offset": 2,
      "type": "<HANGUL>",
      "position": 0
    },
    {
      "token": "opensearch에서도",
      "start_offset": 3,
      "end_offset": 16,
      "type": "<ALPHANUM>",
      "position": 1
    },
    {
      "token": "노리를",
      "start_offset": 17,
      "end_offset": 20,
      "type": "<HANGUL>",
      "position": 2
    },
    {
      "token": "사용할",
      "start_offset": 21,
      "end_offset": 24,
      "type": "<HANGUL>",
      "position": 3
    },
    {
      "token": "수",
      "start_offset": 25,
      "end_offset": 26,
      "type": "<HANGUL>",
      "position": 4
    },
    {
      "token": "있습니다",
      "start_offset": 27,
      "end_offset": 31,
      "type": "<HANGUL>",
      "position": 5
    }
  ]
}

이처럼 분석기의 종류에 따라서 토큰화하는 방식이 달라지고, 이는 검색 결과의 품질과 직결됩니다. Standard 분석기의 경우 Unicode Text Segmentation algorithm에 기반하여 문장을 분리하지만, 한글 형태소 분석은 하지 못한 것을 확인할 수 있습니다. 빌트인 분석기에 대한 자세한 사항은 이곳을 참고해 주십시오.

노리 분석기를 이용한 인덱싱과 검색

이제부터 text_nori_analyzer라는 이름의 인덱스를 생성하고 데이터를 입력(인덱싱)한 후 검색을 해보겠습니다.

인덱스 생성

요청

PUT text_nori_analyzer
{
  "mappings": {
    "properties": {
      "title" : {
        "type": "text",
        "analyzer": "nori"
      }
    }
  }
}

본 예제에서는 간단한 안내를 위해 인덱스 관련 추가설정은 하지 않고 기본값을 사용합니다. 실제 프로덕션 환경에서는 인덱스 생성 시 모범사례에 맞춰 만드시는 것을 추천해 드립니다. title이라는 이름의 필드를 설정하고, 해당 필드의 타입을 text로 analyzer를 nori로 구성했습니다.

결과

{
  "acknowledged": true,
  "shards_acknowledged": true,
  "index": "text_nori_analyzer"
}

데이터 입력

요청

POST text_nori_analyzer/_doc
{
  "title": "이제 OpenSearch에서도 노리를 사용할 수 있습니다."
}

방금 생성한 인덱스의 title 필드에 원하는 한글 문장을 입력합니다. 문서 번호는 직접 지정하지 않고 자동으로 만들어지도록 합니다.

결과

{
  "_index": "text_nori_analyzer",
  "_id": "J9uhPIsBnCctYrB4mUQz",
  "_version": 1,
  "result": "created",
  "_shards": {
    "total": 2,
    "successful": 2,
    "failed": 0
  },
  "_seq_no": 0,
  "_primary_term": 1
}

문서 id가 J9uhPIsBnCctYrB4mUQz로 방금 입력한 한글 문장이 잘 인덱싱되었습니다. 이제, 실제로 이 문서의 토큰이 어떻게 구성되어 있는지를 _termvectors API를 이용해서 확인해 보겠습니다. API와 함께 확인하고자 하는 인덱스명, 문서 id, 필드를 입력합니다.

입력

GET text_nori_analyzer/_termvectors/J9uhPIsBnCctYrB4mUQz?fields=title
  • 주의 : 문서 id는 매번 다르게 생성되므로 실습 시에는 실제로 결과에 나타난 _id를 사용하십시오.

출력

{
  "_index": "text_nori_analyzer",
  "_id": "J9uhPIsBnCctYrB4mUQz",
  "_version": 1,
  "found": true,
  "took": 17,
  "term_vectors": {
    "title": {
      "field_statistics": {
        "sum_doc_freq": 5,
        "doc_count": 1,
        "sum_ttf": 5
      },
      "terms": {
        "opensearch": {
          "term_freq": 1,
          "tokens": [
            {
              "position": 1,
              "start_offset": 3,
              "end_offset": 13
            }
          ]
        },
        "노리": {
          "term_freq": 1,
          "tokens": [
            {
              "position": 4,
              "start_offset": 17,
              "end_offset": 19
            }
          ]
        },
        "사용": {
          "term_freq": 1,
          "tokens": [
            {
              "position": 6,
              "start_offset": 21,
              "end_offset": 23
            }
          ]
        },
        "수": {
          "term_freq": 1,
          "tokens": [
            {
              "position": 9,
              "start_offset": 25,
              "end_offset": 26
            }
          ]
        },
        "있": {
          "term_freq": 1,
          "tokens": [
            {
              "position": 10,
              "start_offset": 27,
              "end_offset": 28
            }
          ]
        }
      }
    }
  }
}

_analyze API를 이용해서 토큰화된 내용과 동일한 것을 확인할 수 있습니다.

검색

요청 1

GET text_nori_analyzer/_search
{
  "query": {
    "match": {
      "title": "이제 OpenSearch에서도 노리를 사용할 수 있습니다."
    }
  }
}

결과 1

{
  "took": 16,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 1,
      "relation": "eq"
    },
    "max_score": 1.4384104,
    "hits": [
      {
        "_index": "text_nori_analyzer",
        "_id": "J9uhPIsBnCctYrB4mUQz",
        "_score": 1.4384104,
        "_source": {
          "title": "이제 OpenSearch에서도 노리를 사용할 수 있습니다."
        }
      }
    ]
  }
}

입력한 문장과 동일한 문장을 검색하면 당연히 결과가 잘 조회됩니다.

요청 2

GET text_nori_analyzer/_search
{
  "query": {
    "match": {
      "title": "opensearch"
    }
  }
}

결과 2

{
  "took": 8,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 1,
      "relation": "eq"
    },
    "max_score": 0.2876821,
    "hits": [
      {
        "_index": "text_nori_analyzer",
        "_id": "J9uhPIsBnCctYrB4mUQz",
        "_score": 0.2876821,
        "_source": {
          "title": "이제 OpenSearch에서도 노리를 사용할 수 있습니다."
        }
      }
    ]
  }
}

opensearch로 검색해도 잘 검색이 됩니다. 그런데 실제 입력한 문서에서는 OpenSearch로 대소문자가 다른데도 어떻게 검색이 되는 것일까요? 서두에서 말씀드린 것처럼 노리 분석기에는 lowercase token filter가 기본적으로 들어가 있어서 대소문자에 상관없이 검색됩니다. 실제로 소문자로 변경되어 들어갔는지는 앞서 확인한 토큰을 참고해 주십시오.

요청 3

GET text_nori_analyzer/_search
{
  "query": {
    "match": {
      "title": "이제 OpenSearch에서도 노리가 됩니다."
    }
  }
}

검색 문장을 조금 바꿔서 다시 검색해 보겠습니다.

결과 3


  "took": 15,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 1,
      "relation": "eq"
    },
    "max_score": 0.5753642,
    "hits": [
      {
        "_index": "text_nori_analyzer",
        "_id": "J9uhPIsBnCctYrB4mUQz",
        "_score": 0.5753642,
        "_source": {
          "title": "이제 OpenSearch에서도 노리를 사용할 수 있습니다."
        }
      }
    ]
  }
}

위의 결과처럼 잘 검색이 됩니다. 문장이 다른데 어떻게 검색이 된 것일까요? OpenSearch에서는 검색을 요청한 필드의 속성에 분석기가 지정되어 있다면, 검색 문장도 동일한 분석기를 통해 토큰화하고 Okapi BM25 알고리즘을 기반으로 검색합니다. 좀 더 자세한 사항은 이곳을 참고해 주십시오. 실제로 아래와 같이 확인해보면 [opensearch, 노리, 되] 가 확인됩니다.

요청 4

GET _analyze
{
  "analyzer": "nori",
  "text": "이제 OpenSearch에서도 노리가 됩니다"
}

결과 4


  "tokens": [
    {
      "token": "opensearch",
      "start_offset": 3,
      "end_offset": 13,
      "type": "word",
      "position": 1
    },
    {
      "token": "노리",
      "start_offset": 17,
      "end_offset": 19,
      "type": "word",
      "position": 4
    },
    {
      "token": "되",
      "start_offset": 21,
      "end_offset": 24,
      "type": "word",
      "position": 6
    }
  ]
}

그렇다면, 노리와 동의어인 nori로 검색하면 어떻게 될까요?

요청 5

GET text_nori_analyzer/_search
{
  "query": {
    "match": {
      "title": "nori"
    }
  }
}

결과5

{
  "took": 4,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 0,
      "relation": "eq"
    },
    "max_score": null,
    "hits": []
  }
}

당연히, nori라는 단어로 토큰화되지 않았기 때문에 검색 결과가 없습니다. 이런 경우라면, 유사어 또는 동의어 검색을 위한 설정이 필요합니다. 자세한 사항은 여기를 참조하세요.

노리 토크나이저를 활용한 커스텀 분석기 만들기

앞서 언급한 것처럼 분석기에는 토크나이저를 포함하고 있습니다. Amazon OpenSearch Service에서는 나만의 필터를 토크나이저와 함께 구성해서 사용할 수 있습니다. 우선 nori_tokenizer를 이용해서 지금까지 사용한 한글 문장을 똑같이 토큰화해 보겠습니다.

요청

GET _analyze
{
  "tokenizer": {
    "type": "nori_tokenizer"
  },
  "text": "이제 OpenSearch에서도 노리를 사용할 수 있습니다."
}

결과

{
  "tokens": [
    {
      "token": "이제",
      "start_offset": 0,
      "end_offset": 2,
      "type": "word",
      "position": 0
    },
    {
      "token": "OpenSearch",
      "start_offset": 3,
      "end_offset": 13,
      "type": "word",
      "position": 1
    },
    {
      "token": "에서",
      "start_offset": 13,
      "end_offset": 15,
      "type": "word",
      "position": 2
    },
    {
      "token": "도",
      "start_offset": 15,
      "end_offset": 16,
      "type": "word",
      "position": 3
    },
    {
      "token": "노리",
      "start_offset": 17,
      "end_offset": 19,
      "type": "word",
      "position": 4
    },
    {
      "token": "를",
      "start_offset": 19,
      "end_offset": 20,
      "type": "word",
      "position": 5
    },
    {
      "token": "사용",
      "start_offset": 21,
      "end_offset": 23,
      "type": "word",
      "position": 6
    },
    {
      "token": "하",
      "start_offset": 23,
      "end_offset": 24,
      "type": "word",
      "position": 7
    },
    {
      "token": "ᆯ",
      "start_offset": 23,
      "end_offset": 24,
      "type": "word",
      "position": 8
    },
    {
      "token": "수",
      "start_offset": 25,
      "end_offset": 26,
      "type": "word",
      "position": 9
    },
    {
      "token": "있",
      "start_offset": 27,
      "end_offset": 28,
      "type": "word",
      "position": 10
    },
    {
      "token": "습니다",
      "start_offset": 28,
      "end_offset": 31,
      "type": "word",
      "position": 11
    }
  ]
}

노리 분석기를 사용한 것에 비해서 더 많은 토큰이 결과로 나왔습니다. 그 이유는 토큰 필터 없이 토크나이저만 거쳤기 때문입니다. 이제 nori_tokenizer를 활용하여 커스텀 분석기를 만들어 보겠습니다.

커스텀 분석기 구성하기

nori_tokenizer를 사용하되, 원하는 캐릭터 필터와 토큰 필터 조합을 구성할 수 있습니다.

Analyze API를 이용한 테스트

요청

GET _analyze
{
  "tokenizer": "nori_tokenizer",
  "char_filter": ["html_strip"],
  "filter": ["nori_number", "nori_readingform", "lowercase"],
  "text": "<b>Start</b> 이천이십삼년 韓國"
}

응답

{
  "tokens": [
    {
      "token": "start",
      "start_offset": 3,
      "end_offset": 12,
      "type": "word",
      "position": 0
    },
    {
      "token": "2023",
      "start_offset": 13,
      "end_offset": 18,
      "type": "word",
      "position": 1
    },
    {
      "token": "년",
      "start_offset": 18,
      "end_offset": 19,
      "type": "word",
      "position": 2
    },
    {
      "token": "한국",
      "start_offset": 20,
      "end_offset": 22,
      "type": "word",
      "position": 3
    }
  ]
}

앞서 설명한 것처럼 문장이 입력되면 캐릭터 필터(char_filter), 토크나이저(tokenizer), 토큰 필터(filter) 순으로 동작하게 됩니다. 따라서, <b>Start</b>의 html 코드가 html_strip에 의해 처리되고, nori_tokenizer로 토큰화됩니다. 그 후에 nori_number는 이천이십삼을 2023으로, nori_readingform은 韓國을 한국으로, lowercase는 Start를 start로 처리합니다.

커스텀 분석기를 이용한 인덱스 생성

PUT text_nori_custom_analyzer
{
  "mappings": {
    "properties": {
      "title" : {
        "type": "text",
        "analyzer": "my_custom_analyzer"
      }
    }
  },
  "settings": {
    "analysis": {
      "analyzer": {
        "my_custom_analyzer" : {
          "type": "custom",
          "char_filter": [
            "html_strip"
          ],
          "tokenizer" : "nori_tokenizer",
          "filter": [
            "nori_number", "nori_readingform", "lowercase"
          ]
        }
      }
    }
  }
}

인덱스를 생성하면서 title 필드에서 사용할 분석기를 my_custom_analyzer는 이름으로 지정하였고, settings.analysis.my_custom_analyzer에서 세부 사항을 구성했습니다.

실제로 앞서 테스트한 결과와 동일한지 확인합니다.

GET text_nori_custom_analyzer/_analyze
{
  "analyzer": "my_custom_analyzer",
  "text": "<b>Start</b> 이천이십삼년 韓國"
}

커스텀 토크나이저로 확장하기

nori_tokenizer는 기본 구성 이외에 decompound_mode, discard_punctuation, user_dictionary, user_dictionary_rules를 설정할 수 있습니다.

PUT text_nori_custom_tokenizer
{
  "mappings": {
    "properties": {
      "title" : {
        "type": "text",
        "analyzer": "my_custom_analyzer"
      }
    }
  },
  "settings": {
    "analysis": {
      "analyzer": {
        "my_custom_analyzer" : {
          "type": "custom",
          "tokenizer": "my_nori_tokenizer"
        }
      },
      "tokenizer": {
        "my_nori_tokenizer" : {
          "type": "nori_tokenizer",
          "decompound_mode": "mixed",
          "discard_punctuation": "true"
        }
      }
    }
  }
}
  • decompound_mode: none, discard(기본값), mixed가 있으며 복합명사를 어떻게 다룰지에 대한 설정을 할 수 있습니다.
    • none: 복합명사를 분리하지 않고 하나의 토큰으로 저장합니다.
    • discard: 복합명사를 분리하여 토큰으로 저장합니다.
    • mixed: 복합명사를 분리하지 않은 토큰과 분리한 토큰을 모두 저장합니다.
  • discard_punctuation: true(기본값), false가 있으며 문장부호 또는 구두점을 어떻게 다룰지에 대한 설정을 할 수 있습니다.

요청

GET text_nori_custom_tokenizer/_analyze
{
  "analyzer": "my_custom_analyzer",
  "text": "강남구청역!"
}
  • 참고: GET <인덱스명>/_analyze를 사용하면, 인덱스 구성 시 정의한 분석기 설정을 기반으로 결과를 확인할 수 있습니다.

결과

{
  "tokens": [
    {
      "token": "강남구청역",
      "start_offset": 0,
      "end_offset": 5,
      "type": "word",
      "position": 0,
      "positionLength": 3
    },
    {
      "token": "강남",
      "start_offset": 0,
      "end_offset": 2,
      "type": "word",
      "position": 0
    },
    {
      "token": "구청",
      "start_offset": 2,
      "end_offset": 4,
      "type": "word",
      "position": 1
    },
    {
      "token": "역",
      "start_offset": 4,
      "end_offset": 5,
      "type": "word",
      "position": 2
    }
  ]
}

decompound_mode가 mixed로 설정되어 있으므로 복합명사를 앞서 설명한 두 가지 방법 모두로 토큰화합니다. 또한 “!”가 사라진 것을 볼 수 있습니다.

이제, 사용자 사전에 대해 알아보겠습니다. 아래와 같이 기존 인덱스를 삭제하고 신규 인덱스를 생성하겠습니다.

DELETE text_nori_custom_tokenizer

PUT text_nori_custom_tokenizer
{
  "mappings": {
    "properties": {
      "title" : {
        "type": "text",
        "analyzer": "my_custom_analyzer"
      }
    }
  },
  "settings": {
    "analysis": {
      "analyzer": {
        "my_custom_analyzer" : {
          "type": "custom",
          "tokenizer": "my_nori_tokenizer"
        }
      },
      "tokenizer": {
        "my_nori_tokenizer" : {
          "type": "nori_tokenizer",
          "decompound_mode": "mixed",
          "discard_punctuation": "true",
          "user_dictionary_rules": ["c++", "워라밸", "먹방"]
        }
      }
    }
  }
}

노리 토크나이저는 mecab-ko-dic 을 사용하고 있지만 때로는 신조어, 업무 용어, 상표 등을 위한 사용자 사전이 필요할 수 있습니다. user_dictionary_rules를 이용해서 사용자 사전을 만들 수 있고, 아래에서 사용자 사전을 적용했을 때와 적용하지 않았을 때를 비교해 볼 수 있습니다.

요청

GET text_nori_custom_tokenizer/_analyze
{
  "analyzer": "my_custom_analyzer",
  "text": "c++ 워라밸 먹방"
}

결과

{
  "tokens": [
    {
      "token": "c++",
      "start_offset": 0,
      "end_offset": 3,
      "type": "word",
      "position": 0
    },
    {
      "token": "워라밸",
      "start_offset": 4,
      "end_offset": 7,
      "type": "word",
      "position": 1
    },
    {
      "token": "먹방",
      "start_offset": 8,
      "end_offset": 10,
      "type": "word",
      "position": 2
    }
  ]
}

반면에, nori_tokenizer의 기본 설정으로 확인을 해보면 아래와 같습니다.

요청

GET _analyze
{
  "tokenizer": "nori_tokenizer",
  "text": "c++ 워라밸 먹방"
}

결과

{
  "tokens": [
    {
      "token": "c",
      "start_offset": 0,
      "end_offset": 1,
      "type": "word",
      "position": 0
    },
    {
      "token": "워라",
      "start_offset": 4,
      "end_offset": 6,
      "type": "word",
      "position": 1
    },
    {
      "token": "배",
      "start_offset": 6,
      "end_offset": 7,
      "type": "word",
      "position": 2
    },
    {
      "token": "ᆯ",
      "start_offset": 6,
      "end_offset": 7,
      "type": "word",
      "position": 3
    },
    {
      "token": "먹",
      "start_offset": 8,
      "end_offset": 9,
      "type": "word",
      "position": 4
    },
    {
      "token": "방",
      "start_offset": 9,
      "end_offset": 10,
      "type": "word",
      "position": 5
    }
  ]
}

Amazon OpenSearch Service에서는 Package를 이용하면 사용자 사전, 동의어, 불용어를 관리할 수 있습니다. 자세한 사항은 이곳을 참조해 주십시오.

S3에 사용자 사전을 업로드 후, Package에 등록합니다. 그리고 등록된 텍스트 사전을 원하는 OpenSearch 도메인에 연결합니다.

아래와 같이 user_dictionary에 analyzers/<Package ID> 를 이용해서 적용할 수 있습니다. 이 Package ID는 OpenSearch 콘솔 화면에서 업로드한 사전의 상세 페이지에서 확인할 수 있습니다.

PUT text_nori_custom_tokenizer_userdic_package
{
  "mappings": {
    "properties": {
      "title" : {
        "type": "text",
        "analyzer": "my_custom_analyzer"
      }
    }
  },
  "settings": {
    "analysis": {
      "analyzer": {
        "my_custom_analyzer" : {
          "type": "custom",
          "tokenizer": "my_nori_tokenizer"
        }
      },
      "tokenizer": {
        "my_nori_tokenizer" : {
          "type": "nori_tokenizer",
          "decompound_mode": "mixed",
          "discard_punctuation": "true",
          "user_dictionary": "analyzers/F255700190"
        }
      }
    }
  }
}

이렇게 Package를 활용하면 사전의 버전을 관리할 수 있을 뿐만 아니라 여러 도메인에 공유할 수 있으며 인덱스의 setting 가시성이 높아집니다.

결론

지금까지 노리를 이용한 기본적인 한글 검색 활용법을 알아보았습니다. 이제 사용자는 한글에 관한 학문적인 지식 없이도 쉽게 고품질의 검색엔진을 구현할 수 있습니다.

이후에, OpenSearch의 여러 기능을 이용해서 실무에서 많이 사용되는 한글 검색 테크닉을 계속 연재하겠습니다.

Sung-il Kim

Sung-il Kim

김성일 Sr Analytics Specialist SA는 데이터 엔지니어와 소프트웨어 엔지니어로 다양한 서비스를 설계하고 개발을 경험하였습니다. 현재는 아마존 웹서비스(AWS)에서 분석 서비스를 잘 활용할 수 있도록 엔터프라이즈 고객을 대상으로 기술적인 도움을 드리고 있습니다.