Home [엘라스틱서치 바이블] 3. 인덱스 설계 -2
Post
Cancel

[엘라스틱서치 바이블] 3. 인덱스 설계 -2

3. 인덱스 설계 -2

3.3 애널라이저와 토크나이저

  • 이전장을 통해 text 필드의 데이터는 애널라이저를 통해 분석돼 여러 텀으로 쪼개져 색인된다는것을 배웠다.
  • 구체적으로 애널라이저가 동작하는 과정과 ES가 자체적으로 제공하는 빌트인 애널라이저를 사용해보고 특정 상황에 맞는 커스텀 애널라이저를 적용하는 방법을 살펴보자.
  • 애널라이저는 0개 이상의 캐릭터 필더, 1개의 토크나이저, 0개 이상의 토큰 필터로 구성된다.

image

  • 문서가 들어오면 위 애널라이저를 통해 텀을 만들고 이를 색인하는 과정을 거치게 된다.

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 요소 안쪽의 데이터를 꺼낸다. &apos; 같은 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&apos;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$와 같은 기호
customcustom_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*

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

  • .
This post is licensed under CC BY 4.0 by the author.

[엘라스틱서치 바이블] 3. 인덱스 설계 - 1

[엘라스틱서치 바이블] 4. 데이터 다루기 -1

Comments powered by Disqus.