이번에 다루어볼 포스팅은 도커로 ES를 띄우기전에 뭔가 커스텀한 이미지를 만들어서 올릴수없을까 하는 생각에 간단히 ES 기본 이미지에 한글 형태소 분석기(Nori) 플러그인이 설치가된 ES docker image를 커스텀하게 만들어보았다.

 

#Dockerfile

FROM docker.elastic.co/elasticsearch/elasticsearch:7.6.2

ENV ES_VOLUME=/usr/share/elasticsearch/data
ENV ES_BIN=/usr/share/elasticsearch/bin

RUN mkdir $ES_VOLUME/dictionary
RUN $ES_BIN/elasticsearch-plugin install --batch analysis-nori

 

간단히 설명하면, 베이스 이미지로 공식 elasticsearch image를 사용하였고, 나중에 사용자 사전이 위치할 도커 볼륨 디렉토리를 잡아주었고, 거기에 사전이 담기는 디렉토리를 생성했다. 그리고 마지막으로 한글 형태소분석기 플러그인을 설치하는 쉘을 실행시켰다. 이렇게 빌드된 elasticsearch image는 한글 형태소분석기가 이미 설치가된 elasticsearch image가 된다. 그래서 굳이 plugin directory를 마운트해서 직접 플러그인을 설치할 필요가 없다. 물론 추후에 필요에 의해서 설치해야한다면 어쩔수 없지만 말이다.

 

여기에 추후 더 커스텀할 내용을 넣으면 될듯하다.

posted by 여성게
:

오늘 다루어볼 내용은 Elasticsearch를 이용한 한글 자동완성 구현이다. 실습을 위한 Elasticsearch는 도커로 세팅을 진행할 것이다. 한글 형태소 분석기가 필요하기 때문에 Elasticsearch docker image를 조금 커스터마이징하여 한글 형태소 분석기(nori)가 설치된 ES 도커 이미지로 도커 컨테이너를 실행시킬 것이다.

 

ES 도커 이미지는 아래 링크를 참조해서 빌드해준다.

 

Elasticsearch - Elasticsearch custom docker image 빌드(엘라스틱서치 커스텀 도커 이미지 생성)

이번에 다루어볼 포스팅은 도커로 ES를 띄우기전에 뭔가 커스텀한 이미지를 만들어서 올릴수없을까 하는 생각에 간단히 ES 기본 이미지에 한글 형태소 분석기(Nori) 플러그인이 설치가된 ES docker image를 커스..

coding-start.tistory.com

docker run
#동의어 사전 사용을 위해 volume mount and data volume mount
> docker run --name elasticsearch -d -p 9200:9200 -p 9300:9300 \
  -e "discovery.type=single-node" \
  -v /home/deploy/elasticsearch_v/dictionary:/usr/share/elasticsearch/data/dictionary \
  -v /home/deploy/elasticsearch_v/data_v:/usr/share/elasticsearch/data \
  es_image_docker_hub

 

Elasticsearch index setting

토크나이저는 한글 형태소분석기인 노리(nori) 형태소 분석기를 이용했고, 자동완성 구현을 위해 edge_ngram filter를 이용하였다.

 

curl --location --request PUT 'localhost:9200/auto_complete' \
--header 'Content-Type: application/json' \
--data-raw '{
  "settings": {
    "index": {
      "analysis": {
        "analyzer": {
          "korean_analyzer": {
            "type": "custom",
            "tokenizer": "korean_tokenizer",
            "filter": [
              "lowercase",
              "korean_posfilter",
              "synonym_filter",
              "edge_ngram_filter_front",
              "edge_ngram_filter_back",
              "trim"
            ]
          }
        },
        "tokenizer": {
          "korean_tokenizer" : {
            "type" : "nori_tokenizer",
            "decompound_mode" : "mixed",
            "user_dictionary":"/usr/share/elasticsearch/data/dictionary/user_dict.txt"
          }
        },
        "filter": {
          "edge_ngram_filter_front": {
            "type": "edgeNGram",
            "min_gram": "1",
            "max_gram": "10",
            "side": "front"
          },
          "edge_ngram_filter_back": {
            "type": "edgeNGram",
            "min_gram": "1",
            "max_gram": "10",
            "side": "back"
          },
          "synonym_filter": {
            "type": "synonym",
            "lenient": true,
            "synonyms_path":"/usr/share/elasticsearch/data/dictionary/synonym.txt"
          },
          "korean_posfilter":{
                "type":"nori_part_of_speech",
                "stoptags":[
                    "E",
                    "IC",
                    "J",
                    "MAG",
                    "MM",
                    "NA",
                    "NR",
                    "SC",
                    "SE",
                    "SF",
                    "SP",
                    "SSC",
                    "SSO",
                    "SY",
                    "UNA",
                    "VA",
                    "VCN",
                    "VCP",
                    "XPN",
                    "XR",
                    "XSA",
                    "XSN",
                    "XSV"
                ]
            }
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "auto_complete": {
        "type": "text",
        "analyzer": "korean_analyzer"
      }
    }
  }
}'

 

만약 자동완성을 위한 필드에 색인할때, 생략할 품사들을 지정하기 위해서는 nori_part_of_speech 필터의 stoptags에 넣어주면 된다. 위 설정은 명사류 혹은 동사류만 색인되도록 품사지정을 하였다. 품사(nori_part_of_speech)에 대한 자세한 사항은 아래 링크를 참조하자.

 

 

Elasticsearch - 4.한글 형태소분석기(Nori Analyzer)

엘라스틱서치 혹은 솔라와 같은 검색엔진들은 모두 한글에는 성능을 발휘하기 쉽지 않은 검색엔진이다. 그 이유는 한글은 다른 언어와 달리 조사나 어미의 접미사가 명사,동사 등과 결합하기 ��

coding-start.tistory.com

 

색인 및 한글 자동완성 쿼리
#index document
curl --location --request POST 'http://localhost:9200/auto_complete/_doc' \
--header 'Content-Type: application/json' \
--data-raw '{
    "auto_complete" : "피자 주문할게요"
}'

 

위 요청으로 문서를 색인하고 아래 쿼리를 날려보자.

 

#match query
curl --location --request POST 'http://localhost:9200/auto_complete/_search' \
--header 'Content-Type: application/json' \
--data-raw '{
    "query" : {
        "match" : {
            "auto_complete" : {
                "query" : "피"
            }
        }
    }
}'

 

마지막으로 동의어를 넣기 위해서는 아래와 같이 index를 close 했다가 open해야한다. 아래 요청을 참조하자.

 

#index close
curl --location --request POST 'http://localhost:9200/auto_complete/_close' \
--data-raw ''
 
 
#index open
curl --location --request POST 'http://localhost:9200/auto_complete/_open' \
--data-raw ''

 

여기까지 아주 간단하게 한글 자동 완성을 구현해보았다. 사실 더 최적화할 것도 많고, 자소 분리등을 넣어서 자음만 넣어도 자동완성을 구현할 수도 있지만, 이번 포스팅에서는 그냥 한글 자동완성이 어떤 식으로 구현되는지 간단하게 다루어보았다.

posted by 여성게
:

 

이번 포스팅에서 다루어볼 내용은 이전 포스팅에서 다룬 한글 형태소분석기를 이용한 기타 고급검색 기법에 대해 다루어볼 것이다. 물론 해당 고급검색에는 간략한 내용만 다루어보고 집계쿼리나 기타 유사도검색 기반 검색들은 다른 포스팅에서 다루어볼 것이다. 이번에 다루어볼 내용들이다.

 

  1. 검색 결과 하이라이팅
  2. 스크립트를 이용한 동적필드 추가
  3. 검색 템플릿을 이용한 동적 쿼리 제공
  4. 별칭을 이용하여 항상 최신 인덱스 유지하기
  5. 스냅샷을 이용한 백업과 복구

위 내용들을 다루어볼 것이다. 검색의 성능을 향상시키거나 그런 내용은 아니지만 알아두면 아주 유용한 기능들이기에 다루어볼 것이다.

 

 

검색결과 하이라이팅

하이라이팅은 문서 검색 결과에 사용자가 입력한 검색어를 강조하는 기능이다. 해당 기능을 통해 사용자가 입력한 검색어가 어디에 일치되는 지는 쉽게 눈으로 확인가능하다. 즉, 사용자의 편의를 위한 기능중 한가지라고 볼 수 있을 것이다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
POST http://localhost:9200/movie_search/_search
 
{
    "size":1,
    "query":{
        "match":{
            "movieNm":"그대 장미"
        }
    },
    "highlight":{
        "fields":{
            "movieNm":{}
        }
    }
}
 
result =>
{
    "took": 2,
    "timed_out": false,
    "_shards": {
        "total": 5,
        "successful": 5,
        "skipped": 0,
        "failed": 0
    },
    "hits": {
        "total": 49,
        "max_score": 10.243236,
        "hits": [
            {
                "_index": "movie_search",
                "_type": "_doc",
                "_id": "cz3KqmkBjjM-ebDbBcoO",
                "_score": 10.243236,
                "_source": {
                    "movieCd": "2009A438",
                    "movieNm": "장미",
                    "movieNmEn": "",
                    "prdtYear": "",
                    "openDt": "",
                    "typeNm": "",
                    "prdtStatNm": "기타",
                    "nationAlt": "",
                    "genreAlt": "",
                    "repNationNm": "",
                    "repGenreNm": "",
                    "directors": [],
                    "companys": []
                },
                "highlight": {
                    "movieNm": [
                        "<em>장미</em>"
                    ]
                }
            }
        ]
    }
}
cs

 

크게 어렵지 않다. 위처럼 쿼리를 날리게 되면 결과로 highlight가 나오는 걸 볼 수 있다. 화면에 뿌려줄때에는 highlight부분을 검색결과 링크로 뿌려주어도 될 것 같다. 그리고 em 태그 말고 다른 태그로 하이라이팅을 하고 싶을 수도 있다. 그럴때에는 몇개의 옵션만 추가해주면된다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
POST http://localhost:9200/movie_search/_search
 
{
    "size":1,
    "query":{
        "match":{
            "movieNm":"그대 장미"
        }
    },
    "highlight":{
        "pre_tags":["<string>"],
        "post_tags":["</string>"],
        "fields":{
            "movieNm":{}
        }
    }
}
 
result =>
{
    "took": 2,
    "timed_out": false,
    "_shards": {
        "total": 5,
        "successful": 5,
        "skipped": 0,
        "failed": 0
    },
    "hits": {
        "total": 49,
        "max_score": 10.243236,
        "hits": [
            {
                "_index": "movie_search",
                "_type": "_doc",
                "_id": "cz3KqmkBjjM-ebDbBcoO",
                "_score": 10.243236,
                "_source": {
                    "movieCd": "2009A438",
                    "movieNm": "장미",
                    "movieNmEn": "",
                    "prdtYear": "",
                    "openDt": "",
                    "typeNm": "",
                    "prdtStatNm": "기타",
                    "nationAlt": "",
                    "genreAlt": "",
                    "repNationNm": "",
                    "repGenreNm": "",
                    "directors": [],
                    "companys": []
                },
                "highlight": {
                    "movieNm": [
                        "<em>장미</em>"
                    ]
                }
            }
        ]
    }
}
cs

 

위와 같이 pre_tags,post_tags 옵션을 주어 하이라이팅할 단어에 태그를 지정해줄 수도 있다.

 

 

스크립트를 이용하여 동적으로 필드 추가하기

엘라스틱서치는 스크립트를 이용해 사용자가 특정 로직을 삽입하는 것이 가능하다. 이러한 방식을 스크립팅이라고 한다. 스크립팅을 이용하면 두 개 이상의 필드 스코어를 하나로 합하거나 계산된 스코어를 특정 수식으로 재계산하는 등의 작업이 가능해진다. 사용자는 이를 활용해 검색 요청시 특정 필드를 선택적으로 반환하거나 필드의 특정 요소를 수정하는 등 광범위한 작업을 할 수 있다.

 

엘라스틱서치에서는 스크립팅을 지원하는 두가지 방식이 존재한다.

  1. config 폴더에 스크립팅을 저장하여 콜하는 방식
  2. 요청바디에 스크립트를 포함하는 방식

그리고 지금 엘라스틱서치에서는 스크립팅 전용 언어인 페인리스(painless)가 개발되어 사용된다. 엘라스틱서치는 기본적으로 업데이트를 허용하지 않는다. 재색인을 통해 설정한 _id의 문서를 삭제하고 다시 생성할 뿐이다. 하지만 _update API를 제공하고 있는데, 이는 내부적으로 스크립팅을 사용하여 업데이트가 이루어지는 것이다. 오늘 다루어볼 스크립트는 문서의 필드를 동적으로 추가하고 삭제하는 예제이다.

 

1
2
3
4
5
6
7
8
9
PUT http://localhost:9200/movie_script/_doc/1
 
{
    "movieList":{
        "Death_Wish":5.5,
        "About_Time":7,
        "Suits":3.5
    }
}
cs

 

 

인덱스 및 문서를 하나 생성해준다.

 

1
2
3
4
5
POST http://localhost:9200/movie_script/_doc/1/_update
 
{
    "script":"ctx._source.movieList.Black_Panther=3.7"
}
cs

 

위와 같이 _update API를 이용하여 내부적으로 스크립트를 작성해 존재하지 않는 필드를 하나 생성해준다. ctx._source는 스크립트에서 제공하는 특수한 문법이다. 이는 색인된 문서에 접근하기 위한 문법이라고 생각하면 된다.

 

1
2
3
4
5
POST http://localhost:9200/movie_script/_doc/1/_update
 
{
    "script":"ctx._source.movieList.remove(\"Suits\")"
}
cs

 

기존에 존재하고 있는 Suits라는 필드를 삭제한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
GET http://localhost:9200/movie_script/_doc/1
 
result =>
{
    "_index": "movie_script",
    "_type": "_doc",
    "_id": "1",
    "_version": 4,
    "found": true,
    "_source": {
        "movieList": {
            "Death_Wish": 5.5,
            "About_Time": 7,
            "Black_Panther": 3.7
        }
    }
}
cs

 

결과값으로 Suits 필드가 삭제되고 Black_Panther가 추가된 것을 볼 수 있다. 이와 같이 스크립트를 이용하면 필드를 동적으로 추가,제거할 수도 있고, 어떠한 결과값을 재정의 하는 것도 가능해진다.

 

 

검색 템플릿을 이용한 동적 쿼리 제공

검색 템플릿은 엘라스틱서치 1.1 버전에 추가된 오래된 기능이다. 하지만 이를 통해 복잡한 검색 로직을 템플릿화하여 저장하고 활용할 수 있기때문에 매우 유용하다. 검색 템플릿의 필드명과 파라미터를 사용해서 쿼리를 전송하고 템플릿에 제공한 파라미터로 실제 검색이 이뤄진다.즉, 검색 템플릿을 사용하여 클라이언트쪽 코드가 아주 간략해지는 것이다.

 

또한 클라이언트 프로그램을 열어 검색의 요구사항이 변경될때마다 코드를 수정하고 다시 배포하는 것이 아니라 엘라스틱서치에 저장돼 있는 템플릿의 기존 쿼리를 수정하고 새 쿼리를 작성하면 되므로 이점이 있다. 검색 템플릿은 Mustache라는 템플릿 엔진을 사용해서 표현된다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
POST http://localhost:9200/_scripts/movie_search_template
 
{
    "script":{
        "lang":"mustache",
        "source":{
            "query":{
                "match":{
                    "movieNm":"{{movie_nm}}"
                }
            }
        }
    }
}
cs

 

검색 템플릿 하나를 생성해준다. 생성이 완료되면 밑의 요청을 보내 검색템플릿이 정상적으로 생성되었는지 정보를 확인한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
GET http://localhost:9200/_scripts/movie_search_template
 
result =>
{
    "_id": "movie_search_template",
    "found": true,
    "script": {
        "lang": "mustache",
        "source": "{\"query\":{\"match\":{\"movieNm\":\"{{movie_nm}}\"}}}",
        "options": {
            "content_type": "application/json; charset=UTF-8"
        }
    }
}
cs

 

이제는 해당 템플릿을 이용하여 검색을 해보자.

 

1
2
3
4
5
6
7
8
9
10
11
POST http://localhost:9200/movie_search/_doc/_search/template
 
{
    "id":"movie_search_template",
    "params":{
        "movie_nm":"그대"
    }
}
 
 
 
cs

 

아이디 값으로 검색 템플릿 이름을 주고 파라미터에 정의된 파라미터 명을 쓰고 값을 넣어주면 된다. 그리고 추후에 검색 템플릿의 변경이 생긴다면 생성과 동일한 요청으로 변경된 템플릿 요청을 보내주면 수정이 완료된다. 이렇게 클라이언트 코드의 수정이 없이도 쿼리의 변경이 가능하니 아주 편리한 기능이다.

 

 

**별칭을 이용해 항상 최신 인덱스 유지하기

엘라스틱서치 클러스터를 운영하는 중에 인덱스 매핑 설정이 변경되거나 인덱스가 깨진다면 기존에 생성된 인덱스를 삭제하고 다시 생성해야 할것이다. 그런데 운영중인 클러스터라면 인덱스 변경을 위하여 인덱스가 삭제되어있는 중에 사용자의 쿼리 요청을 받게되면 인덱스가 존재하지 않는다는 에러메시지를 인덱스가 다시 생성될 때까지 사용자는 보게 될 것이다. 이것이 운영중인 클러스터 상황에 나와야하는 상황일까? 절대 아니다. 절대 나와서는 안되는 상황인 것이다. 이러한 문제를 방지하기 위하여 엘라스틱서치에는 별칭(Alias)라는 기능을 제공한다. 이러한 기능은 Apache Solr에도 거의 동일하게 존재하는 기능이 있다. 필자는 솔라를 이용할때 해당 별칭기능을 이용하여 Active/Standby 컬렉션을 운영했던 경험이 있다. 아마 엘라스틱서치도 비슷한 구성으로 운영가능하지 않을까 생각한다. 그리고 하나의 인덱스에만 별칭을 줄 수 있는 것은 아니다. 여러개의 인덱스에 동일한 별칭을 주어 여러개의 인덱스를 하나의 별칭으로 검색 할 수 있도록 할 수도 있다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
POST http://localhost:9200/_aliases
 
{
    "actions":[
        {
            "add":{"index":"movie_search","alias":"movie_alias"}
        },
        {
            "add":{"index":"movie_search2","alias":"movie_alias"}
        }
    ]
}
 
 
 
cs

 

위와 같이 하나 이상의 인덱스를 동일한 별칭으로 설정할 수 있다. 멀티테넌시 기능을 이용하기 보다는 해당 별칭기능을 이용하는 편이 훨씬 나을 듯하다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
POST http://localhost:9200/movie_alias/_search
 
{
    "size":1,
    "query":{
        "match":{
            "movieNm":"그대 장미"
        }
    }
}
 
 
 
cs

 

별칭으로 검색하는 예제이다. 이와같이 변경이 잦은 인덱스는 별칭을 이용하는 것이 좋다. 예를 들어 매일같이 변경되는 인덱스가 있다고 생각해보자. 그러면 인덱스명을 "인덱스명_타임스탬프" 형식으로 생성하고 다음날에는 이전 인덱스의 별칭을 삭제함과 동시에 새로운 인덱스에 별칭을 주면 사용자 입장에서는 변경되는 것을 알아차리지 못하고 별칭을 이용하여 새로운 인덱스에 요청을 보내게 된다. 이렇게 실무에서는 별칭을 이용하여 항상 최신의 인덱스를 유지하여 사용할 수 있다. 그리고 위에서 잠깐 이야기했지만, Active/StandBy 인덱스를 나누어서 사용해야할 일이 있다면 항상 Active에 별칭을 주고, Switch되는 이벤트가 발생하여 StandBy 인덱스가 Active로 가야한다면 별칭만 StandBy로 추가해주고 Active의 별칭을 삭제하면 Active<->StandBy 스위치가 이러나게 되는 것이다. 이렇게 별칭을 여러가지 용도로 사용가능하다.

 

1
2
3
4
5
6
7
8
9
10
11
12
POST http://localhost:9200/_aliases
 
{
    "actions":[
        {
            "remove":{"index":"movie_search","alias":"movie_alias"}
        }
    ]
}
 
 
 
cs

 

마지막으로 별칭을 삭제하는 요청이다. 해당 요청에는 위와 동일하게 add,remove를 같이 배열로 요청을 보낼 수 있으므로, 인덱스 변경시에는 add와 remove를 동시에 요청을 보내면 될듯하다.

 

 

**스냅샷을 이용한 백업&복구

엘라스틱서치 클러스터를 관리하다 보면 항상 하게 되는 고민 중 하나인 데이터 백업 정책이다. 검색 서버가 항상 안정적이라는 보장은 없기때문에 항상 백업과 복구를 염려해야하는 것은 당연한 것이다. 단순히 데이터 소스를 제공하는 시스템에서 다시 데이터를 Full 색인하면 된다라는 생각을 버려야한다. 데이터가 아주 크다면 아주 오랜시간 동안 색인 작업이 이루어질 것이고, 그러면 그 시간동안 서비스는 불가능 할 것이다. 이러한 고민을 해결해주기 위해 엘라스틱서치는 _snapshot API를 제공해준다. 해당 기능을 이용하여 개별 인덱스를 백업할 수도 있고, 클러스터 전체를 스냅샷으로 만드는 것도 가능하다. 그리고 스냅 샷은 점진적으로 가져온다. 즉, 인덱스의 스냅 샷을 만들 때, Elasticsearch는 동일한 인덱스의 이전 스냅 샷의 일부로 이미 리포지토리에 저장된 데이터를 복사하지 않도록한다. 따라서 클러스터의 스냅 샷을 자주 가져 오는 것이 효율적일 수 있다.

 

1)백업 디렉토리 생성

스냅샷 기능을 이용하기 위해서는 스냅샷이 저장될 물리적인 디렉토리를 생성해주어야 한다.원하는 위치에 디렉토리를 하나 생성해준다.

 

2)백업 디렉토리 등록

엘라스틱서치가 물리적인 백업용 디렉토리를 인식할 수 있도록 elasticsearch.yml 파일에 설정해준다.

백업디렉토리는 위와 같이 배열로 여러 경로를 지정해줄 수 있다. 용도별 스냅샷 디렉토리를 구분할 필요가 있다면 여러개 설정해주어도 될듯하다. 엔진을 재시작한다.

 

3)레포지토리 생성

다음은 스냅샷들을 저장하는 논리적인 저장공간을 만들것이다. 이를 레포지토리라고 부른다. 레포지토리는 설정 내에 물리적인 디렉토리 내부를 저장소로 이용한다. 또한 레포지토리를 생성할 때 다양한 설정 값을 지정할 수 있다. 스냅샷을 백업&복구하는 작업은 시스템 리소스를 많이 사용하기에 몇가지 옵션을 잘 조합해서 사용하는 것이 좋다. 즉, 해당 기능은 엘라스틱서치가 파일시스템을 백업과 복구를 이용하기 위해 등록하는 과정이라고 보면된다.

 

매개변수 설정
location 스냅샷의 물리적인 저장 경로설정.
compress 스냅샷 생성이 압축 수행여부. 이때 데이터 자체는 압축되지 않고 메타데이터만 압축 대상이 된다.
chunk_size 생성되는 파일을 특정 크기로 나누어서 생성할 수 있다. 기본적으로 스냅샷은 하나의 파일로 생성된다.
max_restore_bytes_per_sec 스냅샷 복원 시 속도를 설정한다. 기본적으로 초당 40MB의 속도이다.
max_snapshot_bytes_per_sec 스냅샷 생성(백업)시 속도를 설정한다. 기본적으로 초당 40MB의 속도이다.
readonly 레포지토리를 읽기 전용으로 생성한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
PUT http://localhost:9200/_snapshot/movie_data_backup
 
{
    "type":"fs",
    "settings":{
        "location":"/Users/yun-yeoseong/elasticsearch-file/elasticsearch-6.4.3/backup_snapshot",
        "compress":true
    }
}
 
 
 
cs

 

위 요청으로 레포지토리를 생성해준다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
GET http://localhost:9200/_snapshot/movie_data_backup
 
result=>
{
    "movie_data_backup": {
        "type": "fs",
        "settings": {
            "compress": "true",
            "location": "/Users/yun-yeoseong/elasticsearch-file/elasticsearch-6.4.3/backup_snapshot"
        }
    }
}
 
 
cs

 

위 요청으로 정상적으로 생성된 레포지토리의 정보를 받아올 수 있다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
GET http://localhost:9200/_snapshot/search*,agg*,movie*
 
result=>
{
    "searchexam": {
        "type": "fs",
        "settings": {
            "compress": "true",
            "location": "/Users/yun-yeoseong/elasticsearch-file/book_backup/search_example"
        }
    },
    "aggexam": {
        "type": "fs",
        "settings": {
            "compress": "true",
            "location": "/Users/yun-yeoseong/elasticsearch-file/book_backup/agg_example"
        }
    },
    "movie_data_backup": {
        "type": "fs",
        "settings": {
            "compress": "true",
            "location": "/Users/yun-yeoseong/elasticsearch-file/elasticsearch-6.4.3/backup_snapshot"
        }
    }
}
 
 
cs

 

또한 위와 같이 "*"를 이용하여 여러개의 레포지토리를 조회할 수도 있다.

 

4)스냅샷 생성(백업)

레포지토리가 정상적으로 생성되면 스냅샷을 생성한다. 기본적으로 스냅샷 대상이 되는 인덱스는 더 이상 변경이 없는 인덱스여야만 한다. 변경이 일어나는 도중에 스냅샷이 생성되면 문제가 발생할 수 있다. 그리고 스냅샷 이름은 클러스터 내에서 꼭 유일한 이름이여야한다. 만약 스냅샷 이름이 동일한 것이 존재한다면 에러를 내뱉을 것이다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
PUT http://localhost:9200/_snapshot/movie_data_backup/movie_snapshot_20190511?wait_for_completion=true
 
{
    "indices":"movie_search",
    "ignore_unavailable":true,
    "include_global_state":false
}
 
result=>
{
    "snapshot": {
        "snapshot": "movie_snapshot_20190511",
        "uuid": "m9glqYcyS_mNiv7ZTJmZ8Q",
        "version_id": 6040399,
        "version": "6.4.3",
        "indices": [
            "movie_search"
        ],
        "include_global_state": false,
        "state": "SUCCESS",
        "start_time": "2019-05-11T06:37:08.496Z",
        "start_time_in_millis": 1557556628496,
        "end_time": "2019-05-11T06:37:08.880Z",
        "end_time_in_millis": 1557556628880,
        "duration_in_millis": 384,
        "failures": [],
        "shards": {
            "total": 5,
            "failed": 0,
            "successful": 5
        }
    }
}
cs

 

wait_for_completion=true 옵션을 이용하면 스냅샷 생성이 완료될 때까지 기다린다. 만약 false로 설정하면 스냅샷 초기화가 완료된 직후에 결과를 반환한다. 그리고 내부적으로 백업할 인덱스명을 옵션으로 줄 수 있다.(indices) 하지만 해당 옵션을 사용하지 않으면 기본값으로 해당 클러스터 내부의 모든 인덱스를 백업대상으로 삼는다. 인덱스명은 ","구분으로 여러개를 지정할 수 있다. 그리고 ignore_unavailable 옵션을 true로 설정하면 명시한 indices 옵션에 설정한 인덱스가 존재하지 않는다면 해당 인덱스를 무시하고 다른 인덱스들 백업을 하게 된다. 만약 false로 설정하면 백업을 지정한 인덱스가 존재하지 않으면 에러를 내뱉는다. include_global_state는 클러스터 글로벌 설정을 백업의 일부로 포함시키지 않는다. 만약 해당 설정을 true로 설정하면 모든 샤드에 대해 글로벌 설정이 맞지 않으면 백업이 실패한다. 그리고 만약 스냅샷의 이름의 일부로 날짜등의 데이터를 넣어야 한다면 날짜 표현식을 넣을 수도 있다. 날짜를 넣는다면 데일리 백업에 있어 구분하기 쉬운 방법이 될 것이다. 그리고 배치등을 이용하여 다시 복구할때 소스의 수정없이 날짜 표현식을 이용하면 된다. 해당 표현식은 반드시 URL 엔코딩이 된 문자열이여야한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# PUT /_snapshot/my_backup/<snapshot-{now/d}>
PUT http://localhost:9200/_snapshot/movie_data_backup/%3Csnapshot-%7Bnow%2Fd%7D%3E?wait_for_completion=true
 
{
    "indices":"movie_search",
    "ignore_unavailable":true,
    "include_global_state":false
}
 
result=>
{
    "snapshot": {
        "snapshot": "snapshot-2019.05.11",
        "uuid": "k3GeN2rrS2meMFptYPfAOA",
        "version_id": 6040399,
        "version": "6.4.3",
        "indices": [
            "movie_search"
        ],
        "include_global_state": false,
        "state": "SUCCESS",
        "start_time": "2019-05-11T06:55:51.344Z",
        "start_time_in_millis": 1557557751344,
        "end_time": "2019-05-11T06:55:51.366Z",
        "end_time_in_millis": 1557557751366,
        "duration_in_millis": 22,
        "failures": [],
        "shards": {
            "total": 5,
            "failed": 0,
            "successful": 5
        }
    }
}
cs

 

생성된 스냅샷의 정보를 확인하는 방법이다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
GET http://localhost:9200/_snapshot/movie_data_backup/*
GET http://localhost:9200/_snapshot/movie_data_backup/_all
GET http://localhost:9200/_snapshot/movie_data_backup/snapshot*
 
result=>
{
    "snapshots": [
        {
            "snapshot": "movie_snapshot_20190511",
            "uuid": "m9glqYcyS_mNiv7ZTJmZ8Q",
            "version_id": 6040399,
            "version": "6.4.3",
            "indices": [
                "movie_search"
            ],
            "include_global_state": false,
            "state": "SUCCESS",
            "start_time": "2019-05-11T06:37:08.496Z",
            "start_time_in_millis": 1557556628496,
            "end_time": "2019-05-11T06:37:08.880Z",
            "end_time_in_millis": 1557556628880,
            "duration_in_millis": 384,
            "failures": [],
            "shards": {
                "total": 5,
                "failed": 0,
                "successful": 5
            }
        },
        {
            "snapshot": "snapshot-2019.05.11",
            "uuid": "k3GeN2rrS2meMFptYPfAOA",
            "version_id": 6040399,
            "version": "6.4.3",
            "indices": [
                "movie_search"
            ],
            "include_global_state": false,
            "state": "SUCCESS",
            "start_time": "2019-05-11T06:55:51.344Z",
            "start_time_in_millis": 1557557751344,
            "end_time": "2019-05-11T06:55:51.366Z",
            "end_time_in_millis": 1557557751366,
            "duration_in_millis": 22,
            "failures": [],
            "shards": {
                "total": 5,
                "failed": 0,
                "successful": 5
            }
        }
    ]
}
cs

 

마지막으로 결과값에 포함된 state의 값의 의미이다.

설명
IN_PROGRESS 스냅샷 생성중.
SUCCESS 스냅샷이 정상적으로 생성됨.
FAILED 스냅샷이 정상적으로 생성되지 못하였으며, 어떠한 데이터도 저장되지 않음.
PARTIAL 글로벌 클러스터 상태가 저장되었지만 하나 이상의 샤드의 데이터가 성공적으로 저장되지 않음.
INCOMPATIBLE

스냅 샷은 이전 버전의 Elasticsearch로 작성되었으므로 현재 버전의 클러스터와 호환되지 않음.

 

공식도큐먼트 글

인덱스 스냅 샷을 만드는 과정에서 Elasticsearch는 이미 리포지토리에 저장된 인덱스 파일 목록을 분석하고 마지막 스냅 샷 이후에 작성되었거나 변경된 파일 만 복사합니다. 따라서 여러 스냅 샷을 압축 된 형태로 저장소에 보존 할 수 있습니다. 스냅 샷 프로세스는 비 차단 방식으로 실행됩니다. 스냅 샷을 생성하는 인덱스에 대해 모든 인덱싱 및 검색 작업을 계속 실행할 수 있습니다. 그러나 스냅 샷은 스냅 샷이 생성 된 시점의 인덱스의 특정 시점보기를 나타내므로 스냅 샷 프로세스가 시작된 후 인덱스에 추가 된 레코드는 스냅 샷에 표시되지 않습니다. 스냅 샷 프로세스는 시작되었지만 현재 재배치되지 않은 기본 샤드에 대해 즉시 시작됩니다. 버전 1.2.0 이전에는 클러스터에 스냅 샷에 참여하는 색인의 재배치 또는 초기화 기본이있는 경우 스냅 샷 작업이 실패합니다. 버전 1.2.0부터 Elasticsearch는 Shard의 재배치 또는 초기화가 스냅 샷을 만들기 전에 완료 될 때까지 대기합니다.

각 인덱스의 복사본을 만드는 것 외에도 스냅 샷 프로세스는 영구 클러스터 설정 및 템플릿을 포함하는 글로벌 클러스터 메타 데이터를 저장할 수 있습니다. 일시적 설정 및 등록 된 스냅 샷 저장소는 스냅 샷의 일부로 저장되지 않습니다.

언제든지 하나의 스냅 샷 프로세스 만 클러스터에서 실행할 수 있습니다. 특정 샤드의 스냅 샷이 생성되는 동안이 샤드는 다른 노드로 이동할 수 없으므로 균형 조정 프로세스 및 할당 필터링을 방해 할 수 있습니다. Elasticsearch는 스냅 샷이 완료되면 샤드를 다른 노드로 이동시킬 수 있습니다 (현재 할당 필터링 설정 및 재조정 알고리즘에 따라).

 

스냅샷 복구

1
POST http://localhost:9200/_snapshot/movie_data_backup/movie_snapshot_20190511/_restore
cs

 

위와 같은 요청으로 스냅샷 복구가 가능하다. 하지만 여기서 주의해야 할것은 이미 스냅샷에 저장된 인덱스가 존재할 경우에는 복구에 실패하게 된다. 이말은 즉, 동일한 인덱스가 존재한다면 삭제하고 복구해야한다는 것이다.

 

스냅샷 삭제

 

1
POST http://localhost:9200/_snapshot/movie_data_backup/movie_snapshot_20190511
cs

위의 요청을 보내면 스냅샷이 삭제됨과 동시에 물리적인 디렉토리에서도 삭제되는 것을 볼 수 있다.

 

자세한 설명은 공식 Reference를 참고하면 될듯하다. 스냅샷의 여러 옵션등을 참고 할 수 있다.

 

Snapshot and Restore | Elasticsearch Reference [7.0] | Elastic

The snapshot format can change across major versions, so if you have clusters on different versions trying to write the same repository, snapshots written by one version may not be visible to the other and the repository could be corrupted. While setting t

www.elastic.co

 

지금까지 다양한 검색과 백업/복구 등의 예제를 다루었다. 사실 이것보다 훨씬 다양하고 많은 옵션,기능이 존재한다. 모든 것을 다 다룰 수는 없기때문에 필요한 것은 도큐먼트를 참고하거나 혹은 엘라스틱서치 커뮤니티를 이용해도 좋을 것 같다.

posted by 여성게
:

 

엘라스틱서치 혹은 솔라와 같은 검색엔진들은 모두 한글에는 성능을 발휘하기 쉽지 않은 검색엔진이다. 그 이유는 한글은 다른 언어와 달리 조사나 어미의 접미사가 명사,동사 등과 결합하기 때문에 기본 형태소분석기로는 분석하기 쉽지 않다. 그렇기 때문에 검색엔진을 한글에 적용하기 위해서 별도의 한글 형태소 분석기가 필요하다. 솔라도 물론 가능하고 엘라스틱서치도 역시 한글 형태소 분석기를 내장할 수 있다. 이번 포스팅에서 다루어볼 한글 형태소 분석기는 요즘 뜨고 있는 Nori 형태소분석기를 플러그인 할 것이다. Nori 형태소 분석기는 루씬 프로젝트에서 공식적으로 제공되는 한글 형태소 분석기로써 엘라스틱서치 6.4버전에서 공식적으로 배포됬다. 내부적으로 세종 말뭉치와 mecab-ko-dic 사전을 사용하며, 기존 형태소 분석기와는 다르게 사전을 모두 압축하여 사용하고 있기 때문에 30%이상 빠르고 메모리 사용량도 현저하게 줄었으며, 시스템 전반적으로 영향을 주지 않게 최적화하였다.(아이러니하게 이 형태소분석기를 만든 사람은 한국 사람이 아니다..아마 일본어 형태소 분석기를 만든 사람으로 알고 있다.) 공식적으로 릴리스되었음에도 불구하고 Nori는 기본 플러그인으로 포함되어있지 않기때문에 직접 수동으로 플러그인해야한다(아마 최신에는 기본 내장되어 있을 수도 있다. 만약 그렇다면 댓글 부탁드린다.)

 

Nori 형태소 분석기 플러그인 설치

 

 

플러그인을 위와 같이 설치해준다. 설치가 완료되었다면 plugins 디렉토리에 Nori Analyzer가 설치되어 있을 것이다.

(혹은 ./elasticsearch-plugin list로 analysis-nori가 설치 되었는지 확인해보자. 설치 이후에 엘라스틱서치를 재시작하고 노리 플러그인이 잘 로드되는지 스타트업 로그를 확인하자)

 

직접 사용해보기전에 Nori 형태소 분석기에 대략적인 설명을 하면, Nori 형태소 분석기는 하나의 토크나이저와 두 개의 토큰 필터로 구성돼 있으며 사용자 설정 사전과 불용어 처리를 위한 stoptags를 지원한다.

 

  • nori_tokenizer : 토크나이저
  • nori_part_of_speech : 토큰필터
  • nori_readingform : 토큰필터

 

norir_tokenizer 토크나이저

토크나이저는 루씬에서 사용자 질의 혹은 색인되는 문장을 형태소(토큰)형태로 분리하는 데 사용된다. Nori에서는 해당 토크나이저를 사용할 때에 두 가지 파라미터를 지원한다.

 

  1. decompound_mode : 복합명사를 토크나이저가 처리하는 방식
  2. user_dictionary : 사용자 사전 정의

 

decompound_mode

decompound_mode는 토크나이저가 복합명사를 처리하는 방식을 결정한다. 복합명사가 있을 경우 단어를 어떻게 분리할지를 결정한다.

 

파라미터명 파라미터 값 설명 예제
decompound_mode none 복합명사로 분리하지 않는다 잠실역
사회보장제도
discard 복합명사로 분리하고 원본 데이터는 삭제한다. 잠실역=>[잠실,역]
사회보장제도=>[사회,보장,제도]
mixed 복합명사로 분리하고 원본 데이터도 유지한다. 잠실역=>[잠실,역,잠실역]
사회보장제도=>[사회,보장,제도,사회보장제도]

 

user_dictionary

Nori 형태소 분석기는 위에서도 말햇듯이 시스템사전으로 세종 말뭉치와 mecab-ko-dic 사전을 압축된 형태로 사용한다. 이 사전들 이외로 사용자 사전을 등록하여 사용할 수 있다.(명사,복합명사) 사전 파일은 config 디렉토리 밑에 user_dictionary.txt 와 같은 txt 파일을 만들어 사용한다. 사전파일의 경로는 인덱스 매핑 시 분석기의 파라미터로 지정한다. 

 

사전 등록 방법(config/user_dictionary.txt)

  • 삼성전자
  • 삼성전자 삼성 전자
  • 잠실역

첫번째는 명사 등록이고, 두번째는 복합명사 등록이다. 물론 해당 Nori 사용자 사전 이외에도 엘라스틱서치(루씬의)의 토큰 필터인 synonym filter의 동의어 사전과 stop filter의 불용어 사전등을 관리할 수도 있다. 그렇지만 이것은 사실 Nori 형태소 분석 이후 토큰에 대한 필터이기에 한글 형태소 분석기에 내장된 기능이라고는 볼 수없기에 마지막쯤에 따로 다루겠다.

 

 

Nori Tokenizer를 사용하는 커스텀 분석기 설정

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
설정) PUT http://localhost:9200/korean_analyzer
 
{
    "settings":{
        "number_of_shards": 5,
        "number_of_replicas": 1,
        "analysis":{
            "tokenizer":{
                "korean_nori_tokenizer":{
                    "type":"nori_tokenizer",
                    "decompound_mode":"mixed",
                    "user_dictionary":"user_dictionary.txt"
                }
            },
            "analyzer":{
                "nori_analyzer":{
                    "type":"custom",
                    "tokenizer":"korean_nori_tokenizer"
                }
            }
        }
    }
}
cs

 

korean_analyzer라는 인덱스에 Nori tokenizer를 사용하는 분석기를 사용하는 설정이다. decompound_mode는 mixed이며 config/user_dictionary.txt를 사용하는 nori_tokenizer를 가진 분석기를 설정한것이다. 사실 전처리 필터, 토큰 필터등 더 다양한 옵션을 지정할 수 있다. 지금부터 해당 옵션들을 하나하나 다루어볼 것이다. 우선 위의 분석기가 잘 작동하는지 확인하자.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
설정) POST http://localhost:9200/_analyze
 
{
    "analyzer":"nori",
    "text":"미아역은 어디에 있죠?"
}
 
result =>
 
{
    "tokens": [
        {
            "token": "미아",
            "start_offset": 0,
            "end_offset": 2,
            "type": "word",
            "position": 0
        },
        {
            "token": "역",
            "start_offset": 2,
            "end_offset": 3,
            "type": "word",
            "position": 1
        },
        {
            "token": "어디",
            "start_offset": 5,
            "end_offset": 7,
            "type": "word",
            "position": 3
        },
        {
            "token": "있",
            "start_offset": 9,
            "end_offset": 10,
            "type": "word",
            "position": 5
        }
    ]
}
cs

 

사용자 사전을 적용하지 않은 기본 노리 분석기를 사용했을 때 분석 결과이다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
설정) POST http://localhost:9200/korean_analyzer/_analyze
 
{
    "analyzer":"nori_analyzer",
    "text":"미아역은 어디에 있죠?"
}
 
result =>
 
{
    "tokens": [
        {
            "token": "미아역",
            "start_offset": 0,
            "end_offset": 3,
            "type": "word",
            "position": 0
        },
        {
            "token": "은",
            "start_offset": 3,
            "end_offset": 4,
            "type": "word",
            "position": 1
        },
        {
            "token": "어디",
            "start_offset": 5,
            "end_offset": 7,
            "type": "word",
            "position": 2
        },
        {
            "token": "에",
            "start_offset": 7,
            "end_offset": 8,
            "type": "word",
            "position": 3
        },
        {
            "token": "있",
            "start_offset": 9,
            "end_offset": 10,
            "type": "word",
            "position": 4
        },
        {
            "token": "죠",
            "start_offset": 10,
            "end_offset": 11,
            "type": "word",
            "position": 5
        }
    ]
}
cs

 

여러가지로 결과가 다른 것을 볼 수 있다. 일단 사용자 정의사전이 적용되어 복합명사로 분리되지 않고 하나의 명사로 인식된것만 확인하자.

 

nori_part_of_speech 토큰 필터

nori_part_of_speech 토큰 필터는 품사 태그 세트와 일치하는 토큰을 찾아 제거하는 토큰 필터이다. 즉, 모든 명사를 역색인으로 생성하는 것이 아니라, 역색인 문서를 생성하기 전에 명시한 품사 태그와 일치하는 토큰을 제거하여 역색인을 생성한다. 이러한 삭제할 품사 태그는 stoptags라는 파라미터를 이용하여 지정한다.

 

앞에서 만들었던 korean_analyzer 인덱스의 nori_analyzer에 토큰 필터를 추가할 것이다. 그전에 우리는 이미 생성한 인덱스를 변경하려면 인덱스를 close상태로 만들었다가 인덱스를 변경하고 다시 open해주는 과정을 거처야한다.

 

1
2
3
설정) POST http://localhost:9200/korean_analyzer/_close
 
 
cs

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
설정) PUT http://localhost:9200/korean_analyzer/_settings
 
{
        "analysis":{
            "tokenizer":{
                "korean_nori_tokenizer":{
                    "type":"nori_tokenizer",
                    "decompound_mode":"mixed",
                    "user_dictionary":"user_dictionary.txt"
                }
            },
            "analyzer":{
                "nori_analyzer":{
                    "type":"custom",
                    "tokenizer":"korean_nori_tokenizer",
                    "filter":[
                        "nori_posfilter"    
                    ]
                }
            },
            "filter":{
                "nori_posfilter":{
                    "type":"nori_part_of_speech",
                    "stoptags":[
                        "E",
                        "IC",
                        "J",
                        "MAG",
                        "MM",
                        "NA",
                        "NR",
                        "SC",
                        "SE",
                        "SF",
                        "SH",
                        "SL",
                        "SN",
                        "SP",
                        "SSC",
                        "SSO",
                        "SY",
                        "UNA",
                        "UNKNOWN",
                        "VA",
                        "VCN",
                        "VCP",
                        "VSV",
                        "VV",
                        "VX",
                        "XPN",
                        "XR",
                        "XSA",
                        "XSN",
                        "XSV"
                    ]
                }
            }
        }
}
 
POST http://localhost:9200/korean_analyzer/_open
 
 
cs

 

거의 대부분의 품사를 제거하도록 stoptags에 설정해주었다.  분석기 설정을 변경한 후에 인덱스의 상태를 open으로 바꾸어준다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
설정) POST http://localhost:9200/korean_analyzer/_analyze
 
{
    "analyzer":"nori_analyzer",
    "text":"미아역은 어디에 있죠?"
}
 
result =>
 
{
    "tokens": [
        {
            "token": "미아역",
            "start_offset": 0,
            "end_offset": 3,
            "type": "word",
            "position": 0
        },
        {
            "token": "어디",
            "start_offset": 5,
            "end_offset": 7,
            "type": "word",
            "position": 2
        }
    ]
}
 
 
cs

 

이전과 동일한 분석요청을 보냈음에도 불구하고 변경된 분석기 설정에 의해 대부분의 토큰이 제거된 것을 볼 수 있다. 이렇게 pos 필터를 이용하여 불필요한 토큰들을 제거할 수 있다. 하지만 너무 과한 토큰 삭제는 분석과정에 영향을 미치니 신중하게 결정해야한다. 품사 태그는 아마 엘라스틱서치 공식 홈페이지에 가면 볼 수 있을 것이다. 하지만 중요한 것은 매번 이렇게 close-open 해가며 품사 제거를 테스트하기는 번거롭다. 필자는 직접 Lucene 라이브러리를 임포트해서 자바클래스에서 테스트를 하니 훨씬 편했다. 소스가 필요하다면 댓글남겨주시면 될 것같다.

 

<품사 태그 의미>

예시가 담긴 정보는 아래 테이블 참고(참조: https://prohannah.tistory.com/73)

값(tag) 영문명 한글명 예시
E Verbal endings 어미 사랑/하(E)/다
IC Interjection 감탄사 와우(IC), 맙소사(IC)
J Ending Particle 조사 나/는(J)/너/에게(J)
MAG General Adverb 일반 부사 빨리(MAG)/달리다, 과연(MAG)/범인/은/누구/인가
MAJ Conjunctive adverb 접속 부사 그런데(MAJ), 그러나(MAJ)
MM (*) ES:Modifier(한정사), 루씬 API:Determiner(관형사) 설명이 다름 맨(MM)/밥
NA Unknown 알 수 없음  
NNB Dependent noun (following nouns) 의존명사  
NNBC Dependent noun 의존명사(단위를 나타내는 명사)  
NNG General Noun 일반 명사 강아지(NNG)
NNP Proper Noun 고유 명사 비숑(NNP)
NP Pronoun 대명사 그것(NP), 이거(NP)
NR Numeral 수사 하나(NR)/밖에, 칠(NR)/더하기/삼(NR)
SC(*) Separator (· / :) 구분자 nori_tokenizer가 특수문자 제거
SE(*) Ellipsis 줄임표(...) nori_tokenizer가 특수문자 제거
SF(*) Terminal punctuation (? ! .) 물음표, 느낌표, 마침표 nori_tokenizer가 특수문자 제거
SH Chinese character 한자 中國(SH)
SL Foreign language 외국어 hello(SL)
SN Number 숫자 1(SN)
SP Space 공백  
SSC(*) Closing brackets 닫는 괄호 ),] nori_tokenizer가 특수문자 제거
SSO(*) Opening brackets 여는 괄호 (,[ nori_tokenizer가 특수문자 제거
SY Other symbol 심벌  
UNA Unknown 알 수 없음  
UNKNOWN Unknown 알 수 없음  
VA Adjective 형용사 하얀(VA)/눈
VCN Negative designator 부정 지정사(서술격조사) 사람/이/아니(VCN)/다
VCP Positive designator 긍정 지정사(서술격조사) 사람/이(VCN)/다
VSV Unknown 알 수 없음  
VV Verb 동사 움직이(VV)/다,먹(VV)/다
VX Auxiliary Verb or Adjective 보조 용언 가지/고/싶(VX)/다, 먹/어/보(VX)/다
XPN(*) Prefix 접두사(체언 접두사?) ES에서 매핑되는 단어를 찾지 못함
XR(*) Root 어근 ES에서 매핑되는 단어를 찾기 못함
XSA Adjective Suffix 형용사 파생 접미사 멋/스럽(XSA)/다
XSN(*) Noun Suffix 명사 파생 접미사 ES에서 매핑되는 단어를 찾기 못함
XSV(*) Verb Suffix 동사 파생 접미사 ES에서 매핑되는 단어를 찾기 못함

 

nori_readingform 토큰 필터

사용자 질의에 존재하는 한자를 한글로 변경하는 역할을 하는 필터이다. 위와 같이 동일한 close-open 과정을 통해 분석기를 변경해보자.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
설정) PUT http://localhost:9200/korean_analyzer/_settings
 
{
        "analysis":{
            "tokenizer":{
                "korean_nori_tokenizer":{
                    "type":"nori_tokenizer",
                    "decompound_mode":"mixed",
                    "user_dictionary":"user_dictionary.txt"
                }
            },
            "analyzer":{
                "nori_analyzer":{
                    "type":"custom",
                    "tokenizer":"korean_nori_tokenizer",
                    "filter":[
                        "nori_posfilter",
                        "nori_readingform"
                    ]
                }
            },
            "filter":{
                "nori_posfilter":{
                    "type":"nori_part_of_speech",
                    "stoptags":[
                        "E",
                        "IC",
                        "J",
                        "MAG",
                        "MM",
                        "NA",
                        "NR",
                        "SC",
                        "SE",
                        "SF",
                        "SH",
                        "SL",
                        "SN",
                        "SP",
                        "SSC",
                        "SSO",
                        "SY",
                        "UNA",
                        "UNKNOWN",
                        "VA",
                        "VCN",
                        "VCP",
                        "VSV",
                        "VV",
                        "VX",
                        "XPN",
                        "XR",
                        "XSA",
                        "XSN",
                        "XSV"
                    ]
                }
            }
        }
}
 
 
POST http://localhost:9200/korean_analyzer/_analyze
{
    "analyzer":"nori_analyzer",
    "text":"中國은 어디에 있죠?"
}
result=>
 
{
    "tokens": [
        {
            "token": "중국",
            "start_offset": 0,
            "end_offset": 2,
            "type": "word",
            "position": 0
        },
        {
            "token": "어디",
            "start_offset": 4,
            "end_offset": 6,
            "type": "word",
            "position": 2
        }
    ]
}
cs

 

한자가 한글로 바뀐 것을 볼 수 있다. 마지막으로는 동의어필터, 불용어 필터를 옵션으로 넣어 조금더 풍부한 분석기를 만들어 볼 것이다. 분석기 설정은 이전에 설정한 것을 수정하려고 했으나, 이미 생성된 인덱스에 추가적으로 필터를 등록하면 예외를 내뱉는다.. 이유는 모르지만 어쨌든 새로 인덱스를 생성하였다.(혹시나 이미 생성된 인덱스에 커스텀 필터를 추가하는 방법을 아시는 분은 꼭 댓글로 남겨주시길 바랍니다...)  ->이미 생성된 인덱스도 추가적으로 필터를 추가하는 것이 되는 것으로 확인(테스트 완료)

 

Nori Tokenizer + Nori POS Filter + Nori readingform Filter + Synonym Filter + Stopword Filter

사실 토큰 필터 말고도 전처리 필터를 적용할 수도 있지만 이번 예제는 생략한다. 인덱스를 close-open하는 과정은 동일함으로 생략한다

.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
설정) PUT http://localhost:9200/custom_korean_analyzer
 
{
    "settings":{
        "number_of_shards": 5,
        "number_of_replicas": 1,
        "analysis":{
            "tokenizer":{
                "korean_nori_tokenizer":{
                    "type":"nori_tokenizer",
                    "decompound_mode":"mixed",
                    "user_dictionary":"user_dictionary.txt"
                }
            },
            "analyzer":{
                "nori_analyzer":{
                    "type":"custom",
                    "tokenizer":"korean_nori_tokenizer",
                    "filter":[
                        "nori_posfilter",
                        "nori_readingform",
                        "synonym_filtering",
                        "stop_filtering"
                    ]
                }
            },
            "filter":{
                "nori_posfilter":{
                    "type":"nori_part_of_speech",
                    "stoptags":[
                        "E","IC","J","MAG","MM","NA","NR","SC",
                        "SE","SF","SH","SL","SN","SP","SSC","SSO",
                        "SY","UNA","UNKNOWN","VA","VCN","VCP","VSV",
                        "VV","VX","XPN","XR","XSA","XSN","XSV"
                    ]
                },
                "synonym_filtering":{
                    "type":"synonym"
                    ,"synonyms_path":"synonymsFilter.txt"
                },
                "stop_filtering":{
                    "type":"stop"
                    ,"stopwords_path":"stopFilter.txt"
                }
            }
        }
    }
}
 
 
cs

 

동의어 필터와 불용어 필터를 추가해주었다. 각 경로의 사전의 내용은 아래와 같다.

 

  • 동의어 사전 : 여성게->주인  *여성게를 주인으로 변환해준다. 만약 "여성게,주인" 형태로 사전을 작성한다면 여성게라는 토큰을 날리는 것이 아니고 여성게라는 토큰이 들어오면 역색인에 여성게와 주인 두개의 토큰을 색인한다.
  • 불용어 사전 : 바보  *바보라는 단어가 들어오면 불용어 필터에 걸려 토큰을 날린다.
  • 사용자 사전 : 여성게  *여성게라는 단어를 명사로 인식시켰다.

혹시나 인덱스를 생성하고 사전내용을 추가하였다면 꼭 사전파일을 인식시키기위해 close-open 과정을 추가적으로 수행한다!

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
설정) POST http://localhost:9200/custom_korean_analyzer/_analyze
 
{
    "analyzer":"nori_analyzer",
    "text":"中國은 어디에 있죠? 그리고 여성게은 누구죠? 바보"
}
 
result =>
{
    "tokens": [
        {
            "token": "중국",
            "start_offset": 0,
            "end_offset": 2,
            "type": "word",
            "position": 0
        },
        {
            "token": "어디",
            "start_offset": 4,
            "end_offset": 6,
            "type": "word",
            "position": 2
        },
        {
            "token": "그리고",
            "start_offset": 12,
            "end_offset": 15,
            "type": "word",
            "position": 6
        },
        {
            "token": "주인",
            "start_offset": 16,
            "end_offset": 19,
            "type": "SYNONYM",
            "position": 7
        },
        {
            "token": "누구",
            "start_offset": 21,
            "end_offset": 23,
            "type": "word",
            "position": 9
        }
    ]
}
 
 
cs

 

중국이라는 한자를 한글로 바꿔주었고, 품사 태그 리스트에 해당되면 모두 토큰을 삭제했고, 여성게를 주인으로 동의어 처리를 하였으며 마지막으로 바보라는 토큰을 불용어 처리하여 삭제하였다.

 

이렇게 엘라스틱서치에서 Nori 형태소분석기를 사용하는 방법을 간략히 다루어보았다. 사실 이보다 더 응용할 것이 넘치고 넘친다. 토큰 필터만 수십개이고 다양한 조합을 통해 더욱 용도에 맞는 형태소 분석기로 거듭날 수 있다.

posted by 여성게
:

엘라스틱서치는 인덱스에 저장된 문서를 검색할 수 있도록 다양한 검색기능을 제공한다. 문서는 색인시 설정한 Analyzer에 의해 분석과정을 거쳐 토큰으로 분리되는데, 이러한 Analyzer는 색인 시점 말고도 검색 시점에도 이용된다. 특정 문장이 검색어로 요청되면 분석기를 통해 분석된 토큰의 일치 여부를 판단하여 그 결과에 Score을 매긴다. 이러한 엘라스틱서치에서는 다양한 검색 조건을 주기위하여 Query DSL이라는 특수한 쿼리 문법을 제공한다.

 

1. 검색 API

문장은 색인 시점에 텀으로 분리된다. 검색 시에는 이 텀을 일치시켜야 검색이 가능하다. 엘라스틱서치는 루씬기반이기 때문에 색인 시점에 Analyzer를 통해 분석된 텀을 Term, 출현빈도, 문서번화와 같이 역색인 구조로 만들어 내부적으로 저장한다. 검색 시점에는 Keyword 데이터 타입과 같은 분석이 되지 않는 데이터와 Text 데이터 타입과 같은 분석이 가능한 데이터를 구분해서 분석이 가능할 경우 분석기를 이용해 분석을 수행한다. 이를 통해 검색 시점에도 형태소분석이 되든 되지 않든 텀을 얻을 수 있으며, 해당 텀으로 색인 시점에 생긴 역색인 테이블을 뒤져 문서를 찾고 스코어를 계산하여 결과로 제공한다.

 

1.1. 검색 질의 방식

엘라스틱서치에서 제공하는 검색 API는 기본적으로 Query기반으로 동작한다. 검색 질의에는 검색하고자 하는 각종 Query 조건을 명시할 수 있으며, 동일한 조건을 두 가지 방식으로 표현 가능하다.

 

  1. URI 검색(루씬 스타일)
  2. Request Body 검색

첫번째로 우선 URI방식을 살펴본다. 

 

1.1.1. URI 검색 방식

URI를 이용하는 방식은 HTTP GET 요청을 활용한다. 즉, 쿼리파라미터로 Query를 작성하는 것이다. 간단한 쿼리는 쉽지만 쿼리파라미터의 표현한계로 인해 복잡한 질의는 불가능하다.

 

예시) GET http://localhost:9200/인덱스명/_search?q=필드명:검색값

 

1.1.2. Request Body 검색 방식

Request Body 방식은 HTTP 요청 시 Body에 검색할 칼럼과 검색어를 미리 정의된 JSON 구조로 표현하여 질의하는 방식이다. JSON 구조의 표현을 조금더 효율적으로 하기 위해 엘라스틱서치는 Query DSL이라는 미리 정의된 특별한 문법을 지원한다. URI 검색방식보다 더욱 풍부한 조건의 질의가 가능하다.

 

1
2
3
4
5
6
7
8
9
예시) POST http://localhost:9200/인덱스명/_search
     Header Content-Type:application/json
Request Body {
                "query":{
                   "term":{
                      "typeNm":"장편"
                   }
                }
             }
cs

 

그러면 각 검색 표현법의 장단점은 무엇일까?

 

1.2.1. URI 검색 방식

URI 검색은 Request Body 검색에 비해 단순하고 사용하기 편하지만 복잡한 질의문을 입력하기 힘들다는 치명적인 단점이 있다. 또한 URI 검색을 이용할 경우에는 엘라스틱서치에서 제공하는 모든 검색 API 옵션을 사용할 수 없다. 이유는 쿼리파라미터 표현법에는 표현상 한계가 있기 때문이다. 하지만 간단한 쿼리 혹은 테스트에서는 간편함을 제공하기도 함으로 URI 검색 방식의 주요 파라미터를 알아보자.

 

파라미터 기본값 설명
q - 검색을 수행할 쿼리 문자열 조건을 지정.
df - 쿼리에 검색을 수행할 필드가 지정되지 않았을 경우 기본값으로 검색할 필드를 지정한다.
analyzer 검색 대상 필드에 설정된 형태소 분석기(색인 시점 분석기) 쿼리 문자열을 형태소 분석할때 사용할 분석기 지정
analyze_wildcard false 접두어/와일드카드 검색 활성화 여부 지정
default_operator OR 두 개 이상의 검색 조건이 q에 포함된 경우 검색 조건 연산자를 설정한다.
_source true 검색 결과에 문서 본문 포함 여부를 지정
sort - 정렬 기준 필드 지정
from - 검색 시작 위치 지정
size - 반환 결과 개수 지정

 

q 옵션에는 기본적으로 '필드명:검색어' 형태로 입력할 수 있으며, 여러개의 필드를 검색 할때는 공백을 입력한 후에 추가적인 필드명과 검색어를 입력한다. URI 검색 방식의 q 옵션은 Request Body 검색에서 제공하는 Query String Search 옵션과 동일하게 동작한다.

 

예시) http://localhost:9200/인덱스명/_search?q=필드명1:필드값1 AND 필드명2:필드값2&

        analyze_wildcard=true&from=0&size=5&sort=_score:desc,필드명:asc&

       _source_includes=필드명,필드명,필드명,필드명

 

 

1.2.2. Request Body 검색 방식

위의 URI 검색방식 예시를 그대로 Request Body 검색 방식으로 바꾸어보겠다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
POST http://localhost:9200/인덱스명/_search
Header Content-Type : application/json
<Request Body>
{
    "query":{
        "query_string":{
            "default_field":"필드명"
            ,"query":"필드명1:필드값1 AND 필드명2:필드값2"
        }
    }
    ,"from":0
    ,"size":5
    ,"sort":[{
        "_score":{
            "order":"desc"
        }
        ,"필드명":{
            "order":"asc"
        }
    }]
    ,"_source":[
        "필드명"
        ,"필드명"
        ,"필드명"
        ,"필드명"
    ]
}
cs

 

간단한 검색 조건인 경우에는 URI 검색방식이 편해보일 수도 있지만, 구조화된 JSON 구조를 파악하면 더욱 깔끔한 질의작성이 가능하다.

 

 

2. Request Body 검색 방식 - Query DSL 

엘라스틱서치로 검색 질의를 요청할때는 Request Body 방식과 URI 방식 모두 _search API를 이용한다. 하지만 Query DSL 검색을 이용하면 여러개의 질의를 조합하거나 질의 결과에 대해 또 검색을 수행하는 등의 기존 URI 방식 보다 강력한 검색이 가능하다. 엘라스틱서치의 Query DSL은 구조화된 JSON 형태를 사용한다.

 

2.1. Query DSL 구조

 

<요청>

1
2
3
4
5
6
7
8
9
10
{
    "size" : "-->반환받는 결과 개수"
    ,"from" : "-->몇번째 문서부터 가져올지 지정. 기본값은 0이다."
    ,"timeout" : "-->검색 요청시 결과를 받는 데까지 걸리는 시간. timeout을 너무 작게하면
                  전체 샤드에서 timeout을 넘기지 않은 문서만 결과로 출력된다. 기본 값은 무한이다."
    ,"_source" : "-->검색시 필요한 필드만 출력하고 싶을 때 사용"
    ,"query" : "-->검색 조건문이 들어가는 공간"
    ,"aggs" : "-->통계 및 집계 데이터를 사용할때 사용하는 공간"
    ,"sort" : "-->문서 결과의 정렬 조건이 들어가는 공간"
}
cs

 

엘라스틱서치로 쿼리가 요청되면 해당 쿼리를 파싱해서 문법에 맞는 요청인지 검사한 후에 파싱에 성공하면 해당 쿼리를 기반으로 검색을 수행하고, 결과를 JSON으로 돌려준다.

 

<응답>

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
    "took" : "-->쿼리 실행 시간"
    ,"timed_out" : "-->쿼리 시간이 초과할 경우를 나타낸다"
    ,"_shards" : {
        "total" : "-->쿼리를 요청한 전체 샤드 개수"
        ,"successful" : "-->검색 요청에 성공적으로 응답한 샤드 개수"
        ,"failed" : "-->검색 요청에 실패한 샤드 개수"
    }
    ,"hits" : {
        "total" : "-->질의에 매칭된 문서의 전체 개수"
        ,"max_score" : "-->일치하는 문서의 스코어 값중 가장 높은 값"
        ,"hits" : [결과 문서 정보와 스코어 값등을 보여줌]
    }
}
cs

 

만약 JSON문법의 오류가 있다면 오류 결과를 JSON으로 넘겨준다.

 

 

2.2. Query DSL 쿼리와 필터

Query DSL을 이용해 검색 질의를 작성할 때 조금만 조건이 복잡해 지더라도 여러개의 질의를 조합해 사용해야 한다. 이때 작성되는 작은 질의들을 두 가지 형태로 나눌 수 있다. 실제 분석기에 의한 전문 분석이 필요한 경우와 단순히 "YES/NO"로 판단할 수 있는 조건 검색의 경우다. 엘라스틱서치에서는 전자를 쿼리 컨텍스트라고 하고, 후자를 필터 컨텍스트라는 용어로 구분한다.

 

  쿼리 컨텍스트 필터 컨텍스트
용도 전문 검색 시 사용 조건 검색 시 사용
특징

1.분석기에 의해 분석이 수행됨.                    2.연관성 관련 점수 계산                              3.루씬 레벨에서 분석 과정을 거처야 하므로 상대적으로 느림.

1.YES/NO로 단순 판별 가능                           2.연관성 관련 점수 계산 안함                         3.엘라스틱서치 레벨에서 처리가 가능하므로 상대적으로 빠름

사용 예 "삼성전자의 본사는 어디있죠?" 같은 문장 분석

"created_year" 필드값이 2018년도인지 여부 "status" 필드에 "user"라는 코드 포함여부

 

대부분의 경우 쿼리 방식과 필터 방식 중 어떤 방식으로 검색 질의를 표현하더라도 같은 결과를 얻을 수도 있다. 하지만 둘의 용도와 성능차이가 있기 때문에 반드시 용도에 맞는 사용을 하는 것이 좋다.

 

2.2.1. 쿼리 컨텍스트

 

  • 문서가 쿼리와 얼마나 유사한지를 점수로 계산
  • 질의가 요청될 때마다 엘라스틱서치에서 내부의 루씬을 이용해 계산을 수행(결과 캐싱 x)
  • 일반적으로 전문 검색에 이용
  • 캐싱되지 않고 디스크 연산을 수행하기에 상대적으로 느림

 

2.2.2. 필터 컨텍스트

 

  • 쿼리의 조건과 문서가 일치하는지를 구분
  • 별도로 점수 계산은 하지 않고 단순 매칭 여부만 검사
  • 자주 사용되는 필터의 결과는 내부적으로 캐싱
  • 기본적으로 메모리 연산이기에 속도 빠름

 

2.3.1. Query DSL 주요 파라미터

 

Multi Index 검색

기본적으로 모든 검색 요청은 Multi Index, Multi Type 검색이 가능하다. 즉, 여러 인덱스를 검색할때 한번의 요청으로 검색요청을 보낼 수 있다. 검색 요청시 ","로 다수의 인덱스명을 입력한다.

 

예시) POST http://localhost:9200/인덱스명1,인덱스명2/_search ...

 

두개의 인덱스명이 공통적인 필드만 갖고 있다면 두개가 서로다른 스키마 구조이더라도 한번에 검색할 수 있다. 검색 요청시 인덱스명에 "*" 입력이 가능하기 때문에 더욱 손쉬운 멀티 인덱스 검색이 가능하다. 예를 들어 매일매일 날짜별로 인덱스를 생성하여 데이터를 색인하는 로그 수집으로 엘라스틱 서치를 이용한다고 생각해보면, "log-2019-01-01" 형태라고 가정시 "log-2019-*"로 손쉽게 멀티 인덱스 검색이 가능하다. 솔라 검색엔진과 비교하면 아주 강력한 기능이다. 보통 솔라는 엘라스틱서치와 달리 인덱스로 도메인을 구분하지 않고 인스턴스로 도메인을 구분하기 때문에 멀티테넌시를 제공하기 쉽지 않다. 그 말은 즉, 도메인 인스턴스별로 같은 질문을 여러번 요청을 보내야 엘라스틱서치의 멀티 인덱스 검색과 같은 결과를 얻을 수 있는 것이다.(최소한 필자 사용시는 그랬음...솔라도 비슷한 기능이 있을 수도 있음)

 

쿼리 결과 페이징

페이징을 하기 위해서는 두가지 파라미터를 이용한다. from과 size이다. 각 기본값은 0,5로 설정되어 있다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
예시) POST http://localhost:9200/인덱스명/_search - 1Page
{
    "from":0,
    "size":5,
    "query":{
        "term":{
            "fieldName":"value"
        }
    }
}
 
예시) POST http://localhost:9200/인덱스명/_search - 2Page
{
    "from":5,
    "size":5,
    "query":{
        "term":{
            "fieldName":"value"
        }
    }
}
cs

 

엘라스틱서치는 관계형 데이터베이스와는 조금 다르게 페이징된 범위의 문서만 가져오는 것이 아니라, 모든 데이터를 읽고 그 중 필터링하여 몇개의 문서를 가져오는 것이기 때문에 페이지 번호가 높아질수록 쿼리 비용은 비례하여 높아진다.

 

 

쿼리 결과 정렬

엘라스틱서치는 기본적으로 점수를 내림차순으로 결과정렬은 한다. 하지만 점수 이외에 정렬 조건을 주고 싶다면 sort 파라미터를 이용한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
예시) POST http://localhost:9200/인덱스명/_search
{
    "query":{
        "term":{
            "fieldName":"value"
        }
    }
    ,"sort":{
        "_score":{
            "order":"desc"
        }
        ,"fieldName":{
            "order":"asc"
        }
    }
}
 
cs

 

위 예제는 점수가 동일할 시에 추가 정렬 조건으로 특정 필드 값으로 오름차순 정렬을 한다.

 

_source 필드 필터링

검색의 결과로 _source의 모든 항목을 결과로 반환한다.(_source의 모든 항목은 문서의 모든 필드를 포함한 결과) 하지만 모든 필드를 결과로 내보낼 필요가 없을 때도 있다. 그때 사용하는 것이 _source 파라미터이다. 필요한 필드만 결과값으로 출력한다면 네트워크 사용량을 줄여 응답 속도도 빨라질 수 있다.

 

1
2
3
4
5
6
7
8
9
10
예시) POST http://localhost:9200/인덱스명/_search
{
    "query":{
        "term":{
            "fieldName":"value"
        }
    }
    ,"_source":["fieldName1","fieldName2"]
}
 
cs

 

범위검색

숫자나 날짜 등을 특정한 숫자나 날짜가 아니라 범위를 지정하여 질의 요청을 할 수도 있다.

 

문법 연산자 설명
lt < 피연산자보다 작음
gt > 피연산자보다 큼
lte <= 피연산자보다 작거나 같음
gte >= 피연산자보다 크거나 같음

 

2016년부터 2019년까지의 데이터 조회 예시이다.

 

1
2
3
4
5
6
7
8
9
10
11
12
예시) POST http://localhost:9200/인덱스명/_search
{
    "query":{
        "range":{
            "date":{
                "gte":"2016",
                "lte":"2019"
            }
        }
    }
}
 
cs

 

 

operator 설정

엘라스틱서치는 검색 시 문장이 들어올 경우 기본적으로 OR연산으로 동작한다. 하지만 실무에서 더 정확도 높은 결과를 위하여 AND연산을 사용하여 검색할때도 있다. 그때 사용하는 것이 operator 파라미터이다.(기본값은 OR이다)

 

1
2
3
4
5
6
7
8
9
10
11
12
예시) POST http://localhost:9200/인덱스명/_search
{
    "query":{
        "match":{
            "fieldName":{
                "query":"coding start",
                "operator":"and"
            }
        }
    }
}
 
cs

 

coding start가 분석기에 의해 분리되고, 두개의 텀을 and 연산으로 검색을 하게 된다. 즉, 둘다 포함된 문서가 결과로 나올 것이다. 만약 OR 연산이라면 coding과 start라는 단어 둘 중 최소한 하나만 포함이 되어도 결과로 반환될 것이다.

 

minimum_should_match 설정

바로 위에서 보았던 operator 설정중 OR연산을 사용하면 너무 많은 결과가 나오게 된다. 이 경우 텀의 개수가 몇 개 이상 매칭될 때만 검색 결과로 나오게 할 수 있는데 이것이 바로 minimum_should_match 파라미터를 이용하는 것이다. 즉, OR연산자로 AND연산과 비슷한 효과를 낼 수 있게 된다.

 

1
2
3
4
5
6
7
8
9
10
11
12
예시) POST http://localhost:9200/인덱스명/_search
{
    "query":{
        "match":{
            "fieldName":{
                "query":"this is coding start",
                "minimum_should_match" : 3
            }
        }
    }
}
 
cs

 

위의 쿼리의 뜻은 OR연산을 사용하되 4개의 텀중 최소한 3개가 일치해야 결과로 내보낸다라는 쿼리이다.

 

 

fuzziness 설정

fuzziness 파라미터를 이용하면 완벽히 동일한 텀으로 문서를 찾는 Match Query를 유사한 텀의 형태로 검색을 하는 Fuzzy Query로 변경할 수 있다. 이는 레벤슈타인 편집 거리 알고리즘을 기반으로 문서의 필드 값을 여러번 변경하는 방식으로 동작한다. 유사한 검색 결과를 찾기 위해 허용 범위의 텀으로 변경해 가며 문서를 찾아 결과로 출력한다. 예를 들어, 편집 거리수를 2로 설정하면 오차범위가 두 글자 이하인 검색 결과까지 포함해서 결과를 준다. 하지만 한국어에는 적용하기 쉽지 않다.(만약 키워드성 질의가 많은 검색이라면 어느정도는 한국어도 가능하기는 한것같다.) 오차범위 값으로 0,1,2,AUTO 총 4가지 값을 사용가능하다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
예시) POST http://localhost:9200/인덱스명/_search
{
    "query":{
        "match":{
            "movieNm":{
                "query":"곤지엠",
                "fuzziness":1
            }
        }
    }
}
 
 
cs

 

 

위와 같이 질의를 날리면 "곤지암"이라는 영화 제목을 결과값으로 반환받을 수 있다.

 

 

boost 설정

boost 설정으로 관련성이 높은 필드나 키워드에 가중치를 더 줄 수 있게 해준다.

 

1
2
3
4
5
6
7
8
9
10
11
12
예시) POST http://localhost:9200/인덱스명/_search
{
    "query":{
        "multi_match":{
            "query":"Fly",
            "fields":["field1^3","field2"]
        }
    }
}
 
 
 
cs

 

위의 쿼리는 Fly라는 단어가 field1에 있을 경우 해당 문서의 가중치에 곱하기 3을 하여 더 높은 점수를 얻게 된다.

 

 

2.3.2. Query DSL 주요 쿼리

 

Match All Query

match_all 파라미터를 사용하여 색인의 모든 문서를 검색하는 쿼리이다.

 

1
2
3
4
5
6
7
8
9
10
예시) POST http://localhost:9200/인덱스명/_search
{
    "query":{
        "match_all":{}
    }
}
 
 
 
 
cs

 

Match Query

Match Query는 텍스트, 숫자, 날짜 등이 포함된 문장을 형태소 분석을 통해 텀으로 분리한 후 이 텀들을 이용해 검색 질의를 수행한다. 즉, 검색어가 분리돼야 할 경우에 사용해야한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
예시) POST http://localhost:9200/인덱스명/_search
{
    "query":{
        "match":{
            "fieldName":"coding start"
        }
    }
}
 
 
 
 
 
cs

 

위의 쿼리는 형태소분석에 의해 coding, start라는 두개의 텀으로 분리되고 별도의 operator가 없기에 OR연산으로 fieldName이라는 필드에 coding,start 텀을 OR연산으로 검색한다.

 

 

Multi Match Query

multi_match 파라미터를 이용하여 하나의 필드가 아니라 여러개의 필드에 대해서 검색을 수행할 수 있다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
예시) POST http://localhost:9200/인덱스명/_search
{
    "query":{
        "multi_match":{
            "query":"가족",
            "fields":["fieldName1","fieldName2"]
        }
    }
}
 
 
 
 
 
 
cs

 

위에서도 다루어봤지만 multi_match를 사용하면서 특정 필드에 가중치를 부여할 수도 있다.

 

 

Term Query

텍스트 형태의 값을 검색하기 위해 엘라스틱서치는 두 가지 매핑 유형을 지원한다.

 

타입 설명
Text 타입 필드에 데이터가 저장되기 전에 데이터가 Analyzer에 의해 분석되어 역색인 구조로 저장됨.
Keyword 타입 데이터가 분석되지 않고 문장이라도 하나의 텀(분석되지 않은)으로 저장됨

 

바로 이전에 다룬 Match Query는 검색어 매칭 전에 검색 텍스트에 대해 형태소 분석이 들어간다. 하지만 Term Query는 별도의 형태소 분석없이 검색 문자 그대로 해당 문자와 동일한 문서가 있는지 찾는다. 즉, Keyword 데이터 타입을 사용하는 필드를 검색하기 위해서는 Term Query를 사용한다. 그리고 정확히 일치하지 않으면 검색 결과로 나오지 않는다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
예시) POST http://localhost:9200/인덱스명/_search
{
    "query":{
        "term":{
            "fieldName":"코미디"
        }
    }
}
 
 
 
 
 
 
 
cs

 

fieldsName이라는 필드에 코미디라는 정확한 단어가 포함된 문서가 있다면 결과로 돌려준다.

 

 

Bool Query

RDB에서는 다양하게 AND,OR,NOT 등의 조건을 여러개 WHERE절에 사용할 수 있다. 이처럼 엘라스틱서치도 여러개의 조건 절을 연결하여 더욱 정확한 검색결과를 얻을 수 있는 쿼리가 있는데, 바로 boot query이다. 

 

엘라스틱서치 SQL 설명
must : [] AND 칼럼=조건 반드시 조건에 만족하는 문서만 검색
must_not : [] AND 칼럼!=조건 조건을 만족하지 않는 문서가 검색
should : [] OR 칼럼=조건 여러 조건 중 하나 이상을 만족하는 문서 검색
filter : [] 칼럼 IN (조건) 조건을 포함하고 있는 문서를 출력. 해당 파라미터를 사용하면 점수별로 정렬하지 않는다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
예시) POST http://localhost:9200/인덱스명/_search
{
    "query":{
        "bool":{
            "must":[
                {
                    "term":{
                        "repGenreNm":"코미디"    
                    }
                },
                {
                    "match":{
                        "repNationNm":"한국"
                    }
                }
            ],
            "must_not":[
                {
                    "match":{
                        "typeNm":"단편"
                    }
                }
            ]
        }
    }
}
 
 
 
 
 
 
 
 
cs

 

해당 쿼리는 장르가 코미디이면서 제작사 나라가 한국이라는 단어를 포함하고 있으며, 종류가 단편이 아닌 영화를 조회하는 쿼리이다.

 

 

Prefix Query

Prefix Query는 해당 접두어가 있는 모든 문서를 검색하는데 사용한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
예시) POST http://localhost:9200/인덱스명/_search
{
    "query":{
        "prefix":{
            "movieNm":"자전"
        }
    }
}
 
 
 
 
 
 
 
 
 
cs

 

영화제목에 자전이라고 시작하는 영화 제목을 검색 결과로 모두 가져온다.

 

 

Exists Query

필드 값이 null 인  데이터를 제외하고 검색한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
예시) POST http://localhost:9200/인덱스명/_search
{
    "query":{
        "exists":{
            "field":"movieNm"
        }
    }
}
 
 
 
 
 
 
 
 
 
cs

 

Wildcard Query

검색어가 와일드카드와 일치하는 구문을 찾는다. 이때 입력된 검색어는 형태소 분석이 이뤄지지 않는다.

 

와일드카드 옵션 설명
* 문자의 길이와 상관없이 와일드카드와 일치하는 모든 문서를 찾는다
? 지정된 위치의 한 글자가 다른 경우의 문서를 찾는다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
예시) POST http://localhost:9200/인덱스명/_search
{
    "query":{
        "wildcard":{
            "fieldName":"여?"
        }
    }
}
 
 
 
 
 
 
 
 
 
cs

 

여기까지 간단한 엘라스틱서치 검색 API에 대해 다루어보았다. 다음 포스팅에서는 더 다루지 못한 검색 API 나머지 부분을 다룰 예정이다. 혹시 잘못된 점이나 모르는 점 등이 있다면 댓글을 꼭 부탁드린다..

posted by 여성게
:
일상&기타/IT News 2019. 2. 11. 22:56

ElasticSearch(엘라스틱), 22일 서울서 기술 세미나 개최



현재 챗봇을 개발하고 있기 때문에 챗봇의 중요한 역할을 하는 자연어처리, 그리고 검색엔진에 관심이 참 많이 간다. 현재는 Solr(솔라)를 사용중인데 요즘 굉장히 핫한 ElasticSearch도 슬슬 공부를 해볼 예정이다. 그런데 22일 엘리스틱서치 기술 세미나 개최가 있다고 해서 기사를 참조해보았다. 바빠서 갈 수 있을지는 모르겠지만, 혹시 저처럼 검색엔진에 관심이 많으신 분이 이 기사를 보시고 한번 참가 해보셨음 좋겠다는 마음에 기사를 올려봅니다.


▶︎▶︎▶︎네이버뉴스




오픈소스 기반의 실시간 로그분석 및 검색 기술업체인 엘라스틱이 오는 22일 서울에서 글로벌 기술 세미나를 개최한다.

엘라스틱서치코리아(대표 한성엽)는 서울 삼성동 인터컨티넨탈서울코엑스에서 창업자이자 본사 CEO인 샤이 배넌이 참석하는 ‘서울 엘라스틱{온} 투어(Elastic{ONTour Seoul) 2019’를 개최한다고 11일 밝혔다.

샤이 배넌 창업자 외에도 시각화 툴인 ‘키바나(Kibana)’의 최초 개발자인 라시드 칸, 루신 노리 한글분석기 개발자 짐 페렌지 등이 참석한다. 

한성엽 엘라스틱서치코리아 대표는 “2년전 한국에 엘라스틱 지사가 설립된 이후 주요 대기업을 비롯해 다양한 중견·중소기업, 스타트업 등이 자사의 실시간 검색 및 로그분석 솔루션을 도입했다”며 “이번 행사를 통해 전세계 최신 기술 동향 및 엘라스틱의 신기술, 다양한 국내 구축사례 및 파트너 솔루션을 소개할 예정”이라고 말했다.





그나저나...한글 형태소분석기 개발자가 외국인이라니...요즘 가장 핫하고 기능이 좋다는 노리 형태소분석기가 외국인이 만들었다는거에 한번 더 충격...입니다.


posted by 여성게
: