3. 인덱스 설계 -2
3.3 애널라이저와 토크나이저
- 이전장을 통해 text 필드의 데이터는 애널라이저를 통해 분석돼 여러 텀으로 쪼개져 색인된다는것을 배웠다.
- 구체적으로 애널라이저가 동작하는 과정과 ES가 자체적으로 제공하는 빌트인 애널라이저를 사용해보고 특정 상황에 맞는 커스텀 애널라이저를 적용하는 방법을 살펴보자.
- 애널라이저는 0개 이상의 캐릭터 필더, 1개의 토크나이저, 0개 이상의 토큰 필터로 구성된다.
- 문서가 들어오면 위 애널라이저를 통해 텀을 만들고 이를 색인하는 과정을 거치게 된다.
3.3.1 analyze API
- ES는 애널라이저와 각 구성 요소의 동작을 쉽게 테스트해볼수 있도록 API를 제공하고 있다.
1
2
GET _analyze
POST _analyze
1
2
3
4
5
6
7
8
9
### analyze API
POST /_analyze
Host: localhost:9200
Content-Type: application/json
{
"analyzer": "standard",
"text": "Hello, HELLO, World!"
}
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
{
"tokens": [
{
"token": "hello",
"start_offset": 0,
"end_offset": 5,
"type": "<ALPHANUM>",
"position": 0
},
{
"token": "hello",
"start_offset": 7,
"end_offset": 12,
"type": "<ALPHANUM>",
"position": 1
},
{
"token": "world",
"start_offset": 14,
"end_offset": 19,
"type": "<ALPHANUM>",
"position": 2
}
]
}
- standard 애널라이저의 분석 결과를 위와 같이 확인해볼 수 있다.
3.3.2 캐릭터 필터
- 캐릭터 필터는 텍스트를 캐릭터의 스트림으로 받아서 특정한 문자를 추가, 변경, 삭제한다.
- 애널라이저에는 0개 이상의 캐릭터 필터를 지정할 수 있다.
- 각 필터는 순서대로 수행된다.
ES 내장 빌트인 필터
- HTML_strip 캐릭터 필터 :
<b>
와 같은 HTML 요소 안쪽의 데이터를 꺼낸다.'
같은 HTML 엔티티도 디코딩한다. - mapping 캐릭터 필터 : 치환할 대상이 되는 문자와 치환 문자를 맵 형태로 선언한다.
- pattern replace 캐릭터 필터 : 정규 표현식을 이용해서 문자를 치환한다.
1
2
3
4
5
6
7
8
9
### 캐릭터 필터
POST /_analyze
Host: localhost:9200
Content-Type: application/json
{
"char_filter": ["html_strip"],
"text": "<p>I'm so <b>happy</b>!</p>"
}
1
2
3
4
5
6
7
8
9
10
11
{
"tokens": [
{
"token": "\nI'm so happy!\n",
"start_offset": 0,
"end_offset": 32,
"type": "word",
"position": 0
}
]
}
3.3.3 토크나이저
- 토크나이저는 캐릭터 스트림을 받아서 여러 토큰으로 쪼개어 토큰 스크림을 만든다.
- 애널라이저에는 한 개의 토크나이저만 지정할 수 있다.
standard 토크나이저
- 가장 기본적인 토크나이저로, Unicode Text segmentation 알고리즘을 사용하여 텍스트를 단어 단위로 나눈다.
- 대부분의 문장 부호(punctuation symbol)가 사라진다.
- 필드 맵핑에 특정 애널라이저를 지정하지 않으면 기본값으로 적용된다.
keyword 토크나이저
- 들어온 텍스트를 쪼개지 않고 그대로 내보낸다.
1
2
3
4
5
6
7
8
9
### keyword 토크나이저
POST /_analyze
Host: localhost:9200
Content-Type: application/json
{
"tokenizer": "keyword",
"text": "Hello, HELLO, World!"
}
1
2
3
4
5
6
7
8
9
10
11
{
"tokens": [
{
"token": "Hello, HELLO, World!",
"start_offset": 0,
"end_offset": 20,
"type": "word",
"position": 0
}
]
}
rgram 토그나이저
- 텍스트를 min_gram 값 이상, max_gram 값 이하의 단위로 쪼갠다.
- ex. min_gram=2, max_gram=3, input=”hello”
- “he”, “hel”, “el”, “ell”, “ll”, “llo”로 총 5개의 토큰으로 쪼개진다.
1
2
3
4
5
6
7
8
9
10
11
12
POST /_analyze
Host: localhost:9200
Content-Type: application/json
{
"tokenizer": {
"type": "ngram",
"min_gram": 3,
"max_gram": 4
},
"text": "Hello, World!"
}
- 21개의 토큰으로 쪼개지는 결과를 확인할 수 있다.
- 위 결과에서는 “o, W” 나 “ld!” 와 같은 공백 문자나 문장 부호가 포함되어 사실상 활용 의미가 없는 토큰도 포함되는데, 이를 방지하기 위한
token_chars
라는 속성을 제공한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
### ngram 토크나이저2
POST /_analyze
Host: localhost:9200
Content-Type: application/json
{
"tokenizer": {
"type": "ngram",
"min_gram": 3,
"max_gram": 4,
"token_chars": ["letter"]
},
"text": "Hello, World!"
}
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
{
"tokens": [
{
"token": "Hel",
"start_offset": 0,
"end_offset": 3,
"type": "word",
"position": 0
},
{
"token": "Hell",
"start_offset": 0,
"end_offset": 4,
"type": "word",
"position": 1
},
{
"token": "ell",
"start_offset": 1,
"end_offset": 4,
"type": "word",
"position": 2
},
{
"token": "ello",
"start_offset": 1,
"end_offset": 5,
"type": "word",
"position": 3
},
{
"token": "llo",
"start_offset": 2,
"end_offset": 5,
"type": "word",
"position": 4
},
{
"token": "Wor",
"start_offset": 7,
"end_offset": 10,
"type": "word",
"position": 5
},
{
"token": "Worl",
"start_offset": 7,
"end_offset": 11,
"type": "word",
"position": 6
},
{
"token": "orl",
"start_offset": 8,
"end_offset": 11,
"type": "word",
"position": 7
},
{
"token": "orld",
"start_offset": 8,
"end_offset": 12,
"type": "word",
"position": 8
},
{
"token": "rld",
"start_offset": 9,
"end_offset": 12,
"type": "word",
"position": 9
}
]
}
- ngram 토크나이저는 ES에서
RDB LIKE '%검색어%'
와 유사한 검색을 구현하고 싶을때, 자동완성 관련 서비스를 구현하고 싶을때 주로 활용한다. - min / max에 지정한 값만큼의 길이로 토큰을 분리하여 처리하는 토크나이저이다.
- min / max의 값이 5이상으로 높여서 차이가 2이상으로 벌어진다면 분석시도가 실패하기도 한다.
ngram의 token_chars 속성
종류 | 설명 |
---|---|
letter | 언어의 글자로 분류되는 문자 |
digit | 숫자로 분류되는 문자 |
whitespace | 띄어쓰기나 줄바굼 문자 등 공백으로 인식되는 문자 |
punctuation | !나 “ 등 문자 부호 |
symbol | $와 같은 기호 |
custom | custom_token_chars 설정을 통해 따로 지정한 커스텀 문자 |
edge_ngram 토크나이저
- edge_ngram 토크나이저는 ngram 토크나이저와 유사한 동작을 수행한다.
- ngram 토크나이저와 다른 점은 생성된 모든 토큰의 시작 글자를 단어의 시작 글자로 고정시켜서 생성한다는 점이 다르다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
### edge_ngram 토크나이저
POST /_analyze
Host: localhost:9200
Content-Type: application/json
{
"tokenizer": {
"type": "edge_ngram",
"min_gram": 3,
"max_gram": 4,
"token_chars": ["letter"]
},
"text": "Hello, World!"
}
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
{
"tokens": [
{
"token": "Hel",
"start_offset": 0,
"end_offset": 3,
"type": "word",
"position": 0
},
{
"token": "Hell",
"start_offset": 0,
"end_offset": 4,
"type": "word",
"position": 1
},
{
"token": "Wor",
"start_offset": 7,
"end_offset": 10,
"type": "word",
"position": 2
},
{
"token": "Worl",
"start_offset": 7,
"end_offset": 11,
"type": "word",
"position": 3
}
]
}
그 외 토크나이저
- letter 토크나이저 : 공백, 특수문자 등 언어의 글자로 분류되는 문자가 아닌 문자를 만났을떄 쪼갠다.
- whitespace 토크나이저 : 공백문자를 만났을때 쪼갠다.
- pattern 토크나이저 : 정규표현식을 단어의 구분자로 사용하여 쪼갠다.
3.3.4 토큰 필터
- 토큰 필터는 토큰 스트림을 받아서 토큰을 추가/변경/삭제한다.
- 하나의 애널라이저에 토큰 필터를 0개 이상 지정할수 있고, 여러개를지정한 경우 순차적으로 적용된다.
대표적인 토큰필터
- lowercase / uppercase 토큰필터 : 토큰의 내용을 소문자/대문자로 만들어 준다.
- stop 토큰 필터 : 불용어를 지정하여 제거할 수 있다. (ex. the, a, an, in 등)
- synonym 토큰 필터 : 유의어 사전 파일을 지정하여 지정된 유의어를 치환한다.
- pattern_replace 토큰필터 : 정규식을 사용하여 토큰의 내용을 치환한ㄷ.
- stemmer 토큰 필터 : 지원되는 몇몇 언어의 어간 추출을 수행한다.(한국어 지원X)
- trim 토큰 필터 : 토큰의 전후에 위치한 공백 문자를 제거한다.
- truncate 토큰 필터 : 지정한 길이로 토큰을 자른다.
1
2
3
4
5
6
7
8
9
### 3.3.4 token filter 테스트
POST /_analyze
Host: localhost:9200
Content-Type: application/json
{
"filter": [ "lowercase" ],
"text": "Hello, World!"
}
3.3.5 내장 애널라이저
- 애널라이저는 캐릭터 필터 + 토크나이저 + 토큰 필터를 조합하여 구성됨.
- ES에서는 내장 캐릭터 필터, 토크나이저, 토큰 필터를 조합하여 미리 만들어 놓은 다양한 내장 애널라이저가 있다.
내장 애널라이저 종류
- standard 애널라이저 : standard 토크나이저와 lowercase 토큰 필터로 구성(별도로 지정하지 않은 경우 기본으로 적용됨)
- simple 애널라이저 : letter가 아닌 문자 단위로 토큰을 쪼갠 뒤 lowercase 토큰 필터를 적용
- whithspace 애널라이저 : whithspace 토크나이저로 구성되고, 공백문자 단위로 토큰을 쪼갠다.
- stop 애널라이저 : standard와 같지만, 뒤에 stop 토큰 필터를 적용해서 불용어를 제거한다.
- keyword 애널라이저 : 특별히 분석을 실시하지 않고, 하나의 큰 토큰을 그대로 반환한다.
- pattern 애널라이저 : pattern 토크나이저와 lowercase 토큰 필터로 구성된다.
- language 애널라이저 : 여러 언어의 분석을 지원한다.(한국어 지원X)
fingerpint 애널라이저
- 중복 검출에 사용할 수 있는 특별한 핑거프린트용 토큰을 생성
- standard 토크나이저 적용 뒤 lowercase 토큰 필터, ASCII folding 토큰 필터, stop 토큰 필터, fingerprint 토큰 필터를 차례대로 적용한다.(stop 토큰 필터는 기본적으로 비활성화)
- fingerprint 토큰 필터는 토큰을 정렬한 뒤 중복을 제거하고 단일 토큰으로 합쳐버린다.
1
2
3
4
5
6
7
8
9
### 3.3.5 내장 애널라이저
POST /_analyze
Host: localhost:9200
Content-Type: application/json
{
"analyzer": "fingerprint",
"text": "Yes yes, Global said this sentence is consistent and."
}
3.3.6 애널라이저를 매핑에 적용
- 실제 각 필드의 매핑에 애널라이저를 적용하는 방법을 알아보자
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
### 3.3.6 애널라이저를 매핑에 적용
PUT /analyzer_test
Host: localhost:9200
Content-Type: application/json
{
"settings": {
"analysis": {
"analyzer": {
"default": {
"type": "keyword"
}
}
}
},
"mappings": {
"properties": {
"defaultText":{
"type": "text",
"analyzer": "keyword"
},
"standardText": {
"type": "text",
"analyzer": "standard"
}
}
}
}
3.3.7 커스텀 애널라이저
- 내장 애널라이저로 목표하는 바를 달성할 수 없다면 커스텀 애널라이저 사용을 고려할 수 있다.
- 캐릭터 필터, 토크나이저, 토큰 필터를 원하는 대로 조합해 지정한다.
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
### 3.3.7 커스텀 애널라이저
PUT /analyzer_test2
Host: localhost:9200
Content-Type: application/json
{
"settings": {
"analysis": {
"char_filter": {
"my_char_filter": {
"type": "mapping",
"mappings": [
"i. => 1.",
"ii. => 2.",
"iii. => 3.",
"iv. => 4."
]
}
},
"analyzer": {
"my_analyzer": {
"char_filter": [
"my_char_filter"
],
"tokenizer": "whitespace",
"filter": [
"lowercase"
]
}
}
}
},
"mappings": {
"properties": {
"myText":{
"type": "text",
"analyzer": "my_analyzer"
}
}
}
}
my_analyzer 테스트
1
2
3
4
5
6
7
8
9
### 3.3.7 커스텀 애널라이저 테스트
GET /analyzer_test2/_analyze
Host: localhost:9200
Content-Type: application/json
{
"analyzer": "my_analyzer",
"text": "i.Hello ii.World iii.Bye, iv.World!"
}
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
{
"tokens": [
{
"token": "1.hello",
"start_offset": 0,
"end_offset": 7,
"type": "word",
"position": 0
},
{
"token": "2.world",
"start_offset": 8,
"end_offset": 16,
"type": "word",
"position": 1
},
{
"token": "3.bye,",
"start_offset": 17,
"end_offset": 25,
"type": "word",
"position": 2
},
{
"token": "4.world!",
"start_offset": 26,
"end_offset": 35,
"type": "word",
"position": 3
}
]
}
- 커스텀 캐릭터 필터가 잘 적용된것을 알 수 있다.
3.3.8 플러그인 설치를 통한 애널라이저 추가와 한국어 형태소 분석
- 한국어 형태소 분석을 지원하는 기본 애널라이저는 없다.
- 하지만 es가 공식 제공하는 nori 플러그인을 설치하면 한국어를 분석할 수 있다. (일본어 : kuromoji, 중국어 : smartcn)
ES의 플러그인을 설치하는 방법
1
2
3
4
5
# bin/elasticsearch-plugin install [플러그인 이름]
bin/elasticsearch-plugin install analysis-nori
bin/elasticsearch-plugin install analysis-kuromoji
bin/elasticsearch-plugin install analysis-smartcn
docker-compose에서 플러그인을 설치하는 방법
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
version: '3.7'
services:
es01:
image: docker.elastic.co/elasticsearch/elasticsearch:8.11.1
container_name: es01
environment:
- node.name=es01
- cluster.name=es-docker-cluster
- discovery.seed_hosts=es02,es03
- cluster.initial_master_nodes=es01,es02,es03
- bootstrap.memory_lock=true
- xpack.security.enabled=false
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
ulimits:
memlock:
soft: -1
hard: -1
command: >
bash -c '
bin/elasticsearch-plugin install analysis-nori
exec /usr/local/bin/docker-entrypoint.sh
'
volumes:
- ./data01:/usr/share/elasticsearch/data
ports:
- 9200:9200
networks:
- internal-network
- command에 플러그인 설치 명령어 추가해주면 완료
- 근데 뭔가 잘 안되는듯하다. 별도로 nori plugin을 설치되어있는걸로 별도 이미지 빌드해서 docker에 띄우는게 좋을것 같음
1
2
3
4
5
6
7
8
9
### 3.3.8 플러그인 설치를 통한 애널라이즈 추가 및 한국어 형태소 분석
GET /_analyze
Host: localhost:9200
Content-Type: application/json
{
"analyzer": "nori",
"text": "우리는 컴퓨터를 다룬다."
}
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
{
"tokens": [
{
"token": "우리",
"start_offset": 0,
"end_offset": 2,
"type": "word",
"position": 0
},
{
"token": "컴퓨터",
"start_offset": 4,
"end_offset": 7,
"type": "word",
"position": 2
},
{
"token": "다루",
"start_offset": 9,
"end_offset": 12,
"type": "word",
"position": 4
}
]
}
- 애널라이저가 조사를 제거했고, “다룬다”의 어간인 “다루”를 제대로 분리했다.(한국어 형태소를 잘 분석한 것)
3.3.9 노멀라이저
- 노멀라이저는 애널라이저와 비슷한 역할을 하나 적용 대상이 text 타입이 아닌 keyword 타입 필드라는 차이가 있다.
- 또, 애널라이저와는 다르게 단일 토큰을 생성한다.
- 최종적으로 단일토큰만 생성하므로 ASCII folding, lowercase, uppercawse 등 글자 단위로 작업을 수행하는 필터만 적용 가능하다.
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
### 3.3.9 노멀라이저
PUT /normalizer_test
Host: localhost:9200
Content-Type: application/json
{
"settings": {
"analysis": {
"normalizer": {
"my_normalizer": {
"type": "custom",
"char_filter": [],
"filter": [
"asciifolding",
"uppercase"
]
}
}
}
},
"mappings": {
"properties": {
"myNormalizerKeyword": {
"type": "keyword",
"normalizer": "my_normalizer"
},
"lowercaseKeyword": {
"type": "keyword",
"normalizer": "lowercase"
},
"defaultKeyword": {
"type": "keyword"
}
}
}
}
- 위 예제에서는 my_normalizer라는 커승텀 노멀라이저를 만들어서 테스트해본다.
- keyword 타입에는 특별히 설정하지 않으면 아무런 노멀라이저도 적용되지 않는다.(text타입의 standard 애널라이저 적용과는 다른부분)
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
### 3.3.9 test
GET /normalizer_test/_analyze
Host: localhost:9200
Content-Type: application/json
{
"field": "myNormalizerKeyword",
"text": "Happy World!!"
}
### 3.3.9 test2
GET /normalizer_test/_analyze
Host: localhost:9200
Content-Type: application/json
{
"field": "lowercaseKeyword",
"text": "Happy World!!"
}
### 3.3.9 test3
GET /normalizer_test/_analyze
Host: localhost:9200
Content-Type: application/json
{
"field": "defaultKeyword",
"text": "Happy World!!"
}
3.4 템플릿
- 인덱스를 생성할 때마다 인덱스 설정과 매핑, 매핑에 지정할 애널라이저 등을 매번 지정해야한다면 수고가 많이 들어간다.
- 서비스와 데이터 설계에 따라 다를수 있지만, ES를 실무에 적용하다보면 수시로 많은 양의 유사한 구조를 가진 인덱스를 생성해야 할 때가 많다. -이런 경우를 위해 템플릿을 사전에 정의해두고, 인덱스 생성시 사전 정의한 템플릿 설정대로 인덱스를 생성할수 있는 기능을 제공한다.
3.4.1 인덱스 템플릿
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
### 3.4.1 인덱스 템플릿
### 템플릿 정의
PUT /_index_template/my_template
Host: localhost:9200
Content-Type: application/json
{
"index_patterns": [
"pattern_test_index-*",
"another_pattern-*"
],
"priority": 1,
"template": {
"settings": {
"number_of_shards": 2,
"number_of_replicas": 2
},
"mappings": {
"properties": {
"myTextField": {
"type": "text"
}
}
}
}
}
### 템플릿 패턴에 일치하는 인덱스 생성
PUT /pattern_test_index-1
Host: localhost:9200
Content-Type: application/json
### 템플릿 패턴에 일치하는 인덱스 조회
GET /pattern_test_index-1
Host: localhost:9200
Content-Type: application/json
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
{
"pattern_test_index-1": {
"aliases": {},
"mappings": {
"properties": {
"myTextField": {
"type": "text"
}
}
},
"settings": {
"index": {
"routing": {
"allocation": {
"include": {
"_tier_preference": "data_content"
}
}
},
"number_of_shards": "2",
"provided_name": "pattern_test_index-1",
"creation_date": "1702113639646",
"number_of_replicas": "2",
"uuid": "HFi6GQ9tRAG__CrH5Qf5vQ",
"version": {
"created": "8500003"
}
}
}
}
}
3.4.2 컴포넌트 템플릿
- 인덱스 템플릿을 많이 만들어 사용하다 보면 템플릿 간 중복되는 부분이 생긴다.
- 중복되는 부분을 재사용할 수 있는 작은 블록으로 쪼갠 것이 컴포넌트 템플릿이다.
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
### 3.4.2 컴포넌트 템플릿
### 컴포넌트 템플릿 정의1
PUT /_component_template/timestamp_mappings
Host: localhost:9200
Content-Type: application/json
{
"template": {
"mappings": {
"properties": {
"timestamp": {
"type": "date"
}
}
}
}
}
### 컴포넌트 템플릿 정의2
PUT /_component_template/my_shard_settings
Host: localhost:9200
Content-Type: application/json
{
"template": {
"settings": {
"number_of_shards": 2,
"number_of_replicas": 2
}
}
}
컴포넌트 템플릿 사용
- 인덱스 템플릿을 생성할 때 재사용할 컴포넌트 템플릿 블록을 composed_of 항목에 넣으면 된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
### 컴포넌트 템플릿을 사용한 인덱스 템플릿 생성
PUT /_index_template/my_template2
Host: localhost:9200
Content-Type: application/json
{
"index_patterns": [
"timestamp-index-*"
],
"composed_of": ["timestamp_mappings", "my_shard_settings"]
}
### 인덱스 생성
PUT /timestamp-index-001
Host: localhost:9200
Content-Type: application/json
### 인덱스 조회
GET /timestamp-index-001
Host: localhost:9200
Content-Type: application/json
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
{
"timestamp-index-001": {
"aliases": {},
"mappings": {
"properties": {
"timestamp": {
"type": "date"
}
}
},
"settings": {
"index": {
"routing": {
"allocation": {
"include": {
"_tier_preference": "data_content"
}
}
},
"number_of_shards": "2",
"provided_name": "timestamp-index-001",
"creation_date": "1702114010362",
"number_of_replicas": "2",
"uuid": "jrNZeqhlT7KOH1A7GHed3w",
"version": {
"created": "8500003"
}
}
}
}
}
- 템플릿을 이용하여 만든 인덱스가 의도대로 잘 만들어진것을 알 수 있다.
3.4.3 레거시 템플릿
- 인덱스 템플릿, 컴포넌트 템플릿 API는 ES 7.8.0부터 추가된 기능이다.
- 이전 버전에서 사용하던 템플릿 API는 레거시 템플릿이 되었다.
- 이전 버전의 템플릿 기능은
_index_tempalte
대신에_template
을 사용해서 사용 가능하고, 일반적인 사용 방법은 동일하지만, 레거시 템플릿에서는 컴포넌트 템플릿을 조합할 수 없다는 차이가 있다. - index_pattern 적용에서 레거시 템플릿과 충돌이 발생한다면 신규 템플릿을 먼저 매칭하고 이게 없을 경우에만 레거시 템플릿을 확인한다는 점에 유의해야 한다.
3.4.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
34
35
36
37
### 3.4.4 동적 템플릿
PUT /_index_template/dynamic_mapping_template
Host: localhost:9200
Content-Type: application/json
{
"index_patterns": ["dynamic_mapping*"],
"priority": 1,
"template": {
"settings": {
"number_of_shards": 2,
"number_of_replicas": 2
},
"mappings": {
"dynamic_templates": [
{
"my_text": {
"match_mapping_type": "string",
"match": "*_text",
"mapping": {
"type": "text"
}
}
},
{
"my_keyword": {
"match_mapping_type": "string",
"match": "*_keyword",
"mapping": {
"type": "keyword"
}
}
}
]
}
}
}
- 위와 같이 동적 템플릿을 적용하면 필드의 이름을 기준으로 적절히 mapping type을 지정할수 있다.
동적 템플릿 적용시 조건
- match_mapping_type : 데이터 타입을 JSON 파서를 이용해서 확인한다. 더 큰 범위의 데이터 타입 이름을 사용한다.
- boolean, double, long, string, object, dat 등을 사용할 수 있다.
- match / unmatch : 필드의 이름이 지정된 패턴과 일치/불일치하는지 확인한다.
- path_match / patch_unmatch :
match / unmatch
와 동일하게 동작하지만 필드 이름으로 마침표를 사용한 전체 경로를 이용한다.- ex :
my_obejct.name.text*
- ex :
3.4.5 빌트인 인덱스 템플릿
- ES 7.9.0 이상 버전은 미리 정의된 빌트인 인덱스 템플릿을 제공한다.
- 로그나 메트릭을 편리하게 수집하기 위한 X-Pack 전용 추가 기능 Elastic Agent에서 사용하기 위해 내장된 템플릿이다.
metrics-*-*
,logs-*-*
인덱스 패턴에 priority 값 100을 가진 템플릿이 사전 정의되어 있다.- 이를 적용하지 않고 커스텀 템플릿을 적용하고 싶다면 priority 값이 더 높은것으로 지정하면 된다.
3.5 라우팅
- 라우팅은 ES가 인덱스를 구성하는 샤드 중 몇 번 샤드를 대상으로 작업을 수행할지 지정하기 위해 사용되는 값이다.
- 라우팅 값은 문서를 색인할 때 문서마다 하나씩 지정할 수 있다.
- 작업 대상 샤드 번호는 지정된 라우팅 값을 해시한 후 주 샤드의 개수로 나머지 연산을 수행한 ㄱ밧이 된다.
- 라우팅 값을 지정하지 않고 문서를 색인하는 경우 라우팅 기본값은
_id
값이 된다. - 색인시 라우팅 값을 지정했다면 조회/업데이트/삭제/검색 등의 작업에서도 똑같이 라우팅을 지정해야 한다.
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
### 3.5 라우팅
### 인덱스 생성
PUT /routing_test
Host: localhost:9200
Content-Type: application/json
{
"settings": {
"number_of_shards": 5,
"number_of_replicas": 1
}
}
### 라우팅 색인
PUT /routing_test/_doc/1?routing=myid
Host: localhost:9200
Content-Type: application/json
{
"login_id": "myid",
"comment": "hello world",
"created_at": "2020-09-08T22:14:09.123Z"
}
### 라우팅 색인2
PUT /routing_test/_doc/1?routing=myid2
Host: localhost:9200
Content-Type: application/json
{
"login_id": "myid2",
"comment": "hello world2",
"created_at": "2020-09-09T22:14:09.123Z"
}
### 조회(전체 샤드 대상으로 조회가 이루어짐)
GET /routing_test/_search
Host: localhost:9200
Content-Type: application/json
### 조회
GET /routing_test/_search?routing=myid
Host: localhost:9200
Content-Type: application/json
### 조회2
GET /routing_test/_search?routing=myid2
Host: localhost:9200
Content-Type: application/json
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
{
"took": 6,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 2,
"relation": "eq"
},
"max_score": 1.0,
"hits": [
{
"_index": "routing_test",
"_id": "1",
"_score": 1.0,
"_routing": "myid2",
"_source": {
"login_id": "myid2",
"comment": "hello world2",
"created_at": "2020-09-09T22:14:09.123Z"
}
},
{
"_index": "routing_test",
"_id": "1",
"_score": 1.0,
"_routing": "myid",
"_source": {
"login_id": "myid",
"comment": "hello world",
"created_at": "2020-09-08T22:14:09.123Z"
}
}
]
}
}
- 응답 결과의 shards 부분을 살펴보면 5개의 샤드 전체를 대상으로 검색이 수행된 점을 알 수 있다.
- hit된 문서의 _routing 메타 필드 안에 우리가 색인 시 지정한 라우팅 값이 myid가 들어가 있음도 확인 할 수 있다.
- routing 값을 이용해서 조회하는 경우 1개의 샤드만을 대상으로 검색이 수행되었다는 것도 알 수 있다.
운영환경시 유의사항
- 운영환경에서는 문서를 색인하거나 검색할 때 가능한 한 라우팅 값을 지정해 주는 것이 좋다.
- 지금은 복잡한 쿼리를 지정하지도 않았고, 데이터의 양도 적기 때문에 성능상 큰 차이를 느낄 수가 없지만, 데이터가 많다면 성능차이는 매우 크다.
3.5.1 인덱스 내에서의 _id 고유성 보장
- 전체 샤드를 대상으로 검색하지 않는 상황에서 문서 단건을 조회할 때 라우팅 값을 명시하지 않는 경우에는 원하는 문서를 조회하지 못할 수도 있다.
- 문서 조회 API는 샤드 하나를 지정하여 조회를 수행한다. 따라서 라우팅 값이 올바르게 명시되지 않는다면 ES는 이미 색인된 문서가 존재하는데도 다른 샤드에서 문서를 조회한 뒤 요청한 문서가 없다는 응답을 반환할 수 있다.
- 추가로 인덱스 내에서 _id 값의 고유성 검증은 샤드 단위로만 보장이 된다.
- 색인 /조회/업데이트/삭제 작업이 모두 라우팅 수행 이후의 단일 샤드 내에서 이뤄지기 떄문이다.
- 라우팅 값이 다르게 되면 한 인덱스 내에서 같은 _id를 가진 문서가 여러개 생길 수 있다는 점에 유의하자.
3.5.2 인덱스 매핑에서 라우팅을 필수로 지정하기
- 실무에서는 ES를 운영하는 주체, 인덱스를 설계하고 생성하는 주체, 데이터를 색인하는 주체, 색인된 데이터를 조회 및 검색하여 서비스에 사용하는 주체가 각각 다를 수 있다.
- 따라서 담당자들간에 라우팅 지정에 대한 정책을 세우고 인식을 조율할 필요가 있다.
- 다양한 조직에서 여러 사람들과 협업하면 일관된 정책을 유지하는것은 쉽지 않을수 있다. 이런일을 방지하기 위해 인덱스 매핑에서
_routing
메타 필드를 지정하여 라우팅 값 명시를 필수로 설정할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
### 3.5.2 인덱스 매핑에서 라우팅을 필수로 지정하기
### 인덱스 생성
PUT /routing_test2
Host: localhost:9200
Content-Type: application/json
{
"mappings": {
"_routing": {
"required": true
}
}
}
### 인덱스 색인 테스트
PUT /routing_test2/_doc/1
Host: localhost:9200
Content-Type: application/json
{
"comment": "index without routing"
}
- routing_missing_exception이 발생하는것을 확인할 수 있다.
Reference
- .
Comments powered by Disqus.