[엘라스틱서치] 자동완성 방식에 스페이스를 구분할 것인가? - forewalk/elastic GitHub Wiki

Elasticsearch

자동완성


자동완성을 만드는 방법은 여러가지가 있다. 이전에 소개한 n-gram을 이용하는 방식도 가능하고, suggest를 이용한 방식도 가능하다. 각각의 장단점이 있으며, 사이트 특성에 맞게 사용하면 될 듯 하다. 이번엔 자동완성을 만드는 방법이 아닌, 자동완성에 스페이스가 들어가는경우 어떻게 처리할 것인가의 문제를 얘기해보고자 한다. 색인되어 있는 데이터에 만약 주소처럼 스페이스가 포함된 데이터가 있다고 예를 들어보자. "경기도 파주시 야당동" 이 데이터의 자동완성 방법은 구글과 네이버가 다른 방식을 취하고 있다.

구글의 경우: "경기도"까지는 자동완성이지만, 스페이스를 무시한채 "경기도파" 까지 입력하게 되면 자동완성이 사라지는 것을 볼 수 있다. 이는 자동완성에 스페이스를 문자열로 처리하고 있다는 의미이며, 네이버의 경우 다르다.

네이버의 경우: "경기도"까지 자동완성처리가 되며, 스페이스를 무시한채 "경기도파"까지 입력하여도 "경기도 파주시 야당동"이 결과로 나타나게 된다. 이는 원장데이터를 여러개로 분리되어 색인된 결과가 아닌(물론 그렇게 할수도 있겠지만), analyzer를 통해 스페이스를 붙여처리한 것으로 볼 수 있다.

두 사이트의 관점의 차이라고 볼 수 있을 듯 한데, 구글은 "정확도"에 초점을 네이버는 "사용자친화적"에 초점을 맞춘 듯하다.

ES에서도 같은 처리를 진행할 수 있다. 토큰 필터 중, 지붕필터(이름이 재미있다) 라고 하는데, 네이보한 데이터의 분절끼리 어떻게 처리할 것인가를 analyzer한 것이라 볼 수 있다. 참고

만약 해당을 인덱싱할 때, 매핑으로 처리한다면 index-phrases를 사용할 수도 있다. 참고

입력값의 스페이스를 어떻게 처리할 것인가? 단순하지만, 고객의 요구사항이 나올 수 있는 내용이다.


PUT dmap_map_nation_datamap_open_20200108
{
    "mappings" : {
      "properties" : {
        "@timestamp" : {
          "type" : "date"
        },
        "@version" : {
          "type" : "keyword"
        },
        "brmCls1Nm" : {
          "type" : "keyword"
        },
        "brmCls2Nm" : {
          "type" : "keyword"
        },
        "dataNodeId" : {
          "type" : "keyword"
        },
        "docContent" : {
          "type" : "text",
          "analyzer" : "nori_analyzer",
          "fielddata" : true
        },
        "docId" : {
          "type" : "keyword"
        },
        "docTitle" : {
          "type" : "text",
          "fields" : {
            "completion" : {
              "type" : "completion",
              "analyzer" : "complete_analyzer",
              "preserve_separators" : true,
              "preserve_position_increments" : true,
              "max_input_length" : 50
            }
          },
          "analyzer" : "nori_analyzer",
          "fielddata" : true
        },
        "holdColList" : {
          "type" : "text"
        },
        "holdColListArray" : {
          "type" : "keyword"
        },
        "lodDt" : {
          "type" : "date"
        },
        "openDataYn" : {
          "type" : "keyword"
        },
        "openHoldColList" : {
          "type" : "text"
        },
        "openHoldColListArray" : {
          "type" : "keyword",
          "fields" : {
            "chosung" : {
              "type" : "text",
              "analyzer" : "chosung_index_analyzer",
              "search_analyzer" : "chosung_search_analyzer"
            },
            "eng2kor" : {
              "type" : "text",
              "analyzer" : "standard",
              "search_analyzer" : "eng2kor_analyzer"
            },
            "nori" : {
              "type" : "text",
              "analyzer" : "nori_analyzer"
            }
          }
        },
        "orgCd" : {
          "type" : "keyword"
        },
        "orgNm" : {
          "type" : "keyword"
        },
        "orgNmArray" : {
          "type" : "keyword",
          "fields" : {
            "chosung" : {
              "type" : "text",
              "analyzer" : "chosung_index_analyzer",
              "search_analyzer" : "chosung_search_analyzer"
            },
            "eng2kor" : {
              "type" : "text",
              "analyzer" : "standard",
              "search_analyzer" : "eng2kor_analyzer"
            },
            "nori" : {
              "type" : "text",
              "analyzer" : "nori_analyzer"
            }
          }
        },
        "searchKeyword" : {
          "type" : "keyword"
        },
        "searchKeywordArray" : {
          "type" : "keyword",
          "fields" : {
            "chosung" : {
              "type" : "text",
              "analyzer" : "chosung_index_analyzer",
              "search_analyzer" : "chosung_search_analyzer"
            },
            "eng2kor" : {
              "type" : "text",
              "analyzer" : "standard",
              "search_analyzer" : "eng2kor_analyzer"
            },
            "nori" : {
              "type" : "text",
              "analyzer" : "nori_analyzer"
           }
          }
        }
      }
    },
    "settings" : {
      "index" : {
        "max_ngram_diff" : "20",
        "max_shingle_diff": "5",
        "analysis" : {
          "filter" : {
            "edge_ngram_front" : {
              "min_gram" : "1",
              "side" : "front",
              "type" : "edgeNGram",
              "max_gram" : "20"
            },
            "synonym" : {
              "type" : "synonym",
              "synonyms_path" : "dictionary/synonyms.txt"
            },
            "stop" : {
              "type" : "stop",
              "stopwords_path" : "dictionary/stopwords.txt"
            },
            "shingle":{
              "type": "shingle",
              "output_unigrams":true,
              "token_separator":"",
              "max_shingle_size":5
            },
            "chosung" : {
              "type" : "javacafe_chosung"
            },
            "nori_stop" : {
              "type" : "nori_part_of_speech",
              "stoptags" : [
                "E",
                "IC",
                "J",
                "MAG",
                "MM",
                "NR",
                "SF",
                "SH",
                "SP",
                "SL",
                "SN",
                "SSC",
                "SSO",
                "SC",
                "SY",
                "UNKNOWN",
                "SE",
                "XPN",
                "XSN",
                "VA",
                "VCN",
                "VCP",
                "VV",
                "VX",
                "XPN",
                "XR",
                "XSA",
                "XSV",
                "UNA",
                "NA",
                "VSV"
              ]
            }
          },
          "analyzer" : {
            "chosung_index_analyzer" : {
              "filter" : [
                "chosung",
                "lowercase",
                "trim",
                "edge_ngram_front"
              ],
              "type" : "custom",
              "tokenizer" : "keyword"
            },
            "eng2kor_analyzer" : {
              "filter" : [
                "trim",
                "lowercase",
                "javacafe_eng2kor",
                "synonym",
                "stop"
              ],
              "type" : "custom",
              "tokenizer" : "standard"
            },
            "nori_analyzer" : {
              "filter" : [
                "nori_stop",
                "lowercase",
                "trim",
                "stop",
                "synonym"
              ],
              "type" : "custom",
              "tokenizer" : "nori_tokenizer"
            },
            "chosung_search_analyzer" : {
              "filter" : [
                "chosung",
                "lowercase",
                "trim"
              ],
              "type" : "custom",
              "tokenizer" : "keyword"
            },
            "complete_analyzer" : {
              "filter" : [
                "trim",
                "lowercase",
                "shingle"
              ],
              "type" : "custom",
              "tokenizer" : "letter"
            }
          },
          "tokenizer" : {
            "ngram_tokenizer" : {
              "token_chars" : [
                "letter",
                "digit",
                "punctuation",
                "symbol"
              ],
              "min_gram" : "1",
              "type" : "ngram",
              "max_gram" : "20"
            },
            "nori_tokenizer" : {
              "mode" : "MIXED",
              "type" : "nori_tokenizer",
              "user_dictionary" : "dictionary/userdic.txt"
            }
          }
        }
      }
    }
  }
}