Intro::
인덱스의 단위에서 이루어지는 설정들과 데이터 명세인 인덱스 매핑에 대해 알아봅시다.
Settings
인덱스를 처음 생성한 뒤 GET <인덱스명> 으로 조회하면 settings와 mappings 정보를 확인할 수 있습니다.
GET my_index/_settings 나 GET my_index/_mappings를 추가해서 볼 수 있습니다.
number_of_shards, number_of_replicas
프라이머리 샤드 수와 레플리카는 number_of_shards, number_of_replicas에서 설정이 가능합니다. 대부분의 설정들은 settins 아래의 index 아래 설정에 명시되는데 index 레벨은 생략하고 입력하여도 정상 입력이 됩니다.
// my_index 인덱스 생성 - 괄호 { } 안에 하위 값 지정
PUT my_index
{
"settings": {
"index": {
"number_of_shards": 3,
"number_of_replicas": 1
}
}
}
// my_index 인덱스 생성 - 마침표 . 으로 하위 값 지정
PUT my_index
{
"settings": {
"index.number_of_shards": 3,
"index.number_of_replicas": 1
}
}
// my_index 인덱스 생성 - index 생략
PUT my_index
{
"settings": {
"number_of_shards": 3,
"number_of_replicas": 1
}
}
JSON
복사
•
number_of_shard
◦
처음 생성 후 바꿀 수 없다.
◦
shrink API 혹은 split API를 이용해서 샤드 수를 변경하는 방법이 존재하지만 인덱스를 close해야 하고, 파일 재배치하는 작업을 하는 복잡한 과정이기에 불가능하다고 생각하는 것이 좋다.
•
number_of_replicas
◦
다이나믹하게 변경 가능하다.
// my_index 인덱스의 number_of_replicas 값 변경
PUT my_index/_settings
{
"number_of_replicas": 2
}
JSON
복사
refresh_interval
Elasticsearch 에서 세그먼트가 만들어지는 리프레시 타임을 설정하는 값인데 기본은 1초(1s) 입니다. number_of_replicas와 마찬가지로 다이나믹 설정입니다.
PUT my_index
{
"settings": {
"refresh_interval": "30s"
}
}
JSON
복사
analyzer, tokenizer, filter
PUT my_index
{
"settings": {
"analysis": {
"analyzer": {
"my_analyzer": {
"type": "custom",
"char_flter": [ "...", "..." ... ]
"tokenizer": "...",
"filter": [ "...", "..." ... ]
}
},
"char_filter":{
"my_char_filter":{
"type": "…"
...
}
}
"tokenizer": {
"my_tokenizer":{
"type": "…"
...
}
},
"filter": {
"my_token_filter": {
"type": "…"
...
}
}
}
}
}
JSON
복사
매핑 - Mappings
동적(Dynamic) 매핑
Elasticsearch는 동적 매핑을 지원하기 때문에 미리 정의하지 않아도 인덱스에 도큐먼트를 새로 추가하면 자동으로 매핑이 생성됩니다.
예시로 인덱스가 없는 상태에서 도큐먼트를 인덱스에 입력하면 자동으로 매핑이 됩니다.
// books 인덱스가 없는 상태에서 도큐먼트 입력
PUT books/_doc/1
{
"title": "Romeo and Juliet",
"author": "William Shakespeare",
"category": "Tragedies",
"publish_date": "1562-12-01T00:00:00",
"pages": 125
}
JSON
복사
// books 인덱스의 매핑 확인
GET books/_mapping
JSON
복사
// books 인덱스의 매핑 확인 결과
{
"books" : {
"mappings" : {
"properties" : {
"author" : {// 필드값
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"category" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"pages" : {
"type" : "long"
},
"publish_date" : {
"type" : "date"
},
"title" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
}
}
}
}
}
JSON
복사
각 필드값에서 결국에 텀을 검색해 나아간다고 생각하면 이해하기 쉽다.
매핑 정의
// 새로운 인덱스 설정
PUT <인덱스명>
{
"mappings": {
"properties": {
"<필드명>":{
"type": "<필드 타입>"
… <필드 설정>
}
…
}
}
}
// 기존 매핑에 필드 추가
PUT <인덱스명>/_mapping
{
"properties": {
"<추가할 필드명>": {// 기존 필드명과 중복된다면 오류 발생
"type": "<필드 타입>"
… <필드 설정>
}
}
}
JSON
복사
이미 만들어진 매핑에 필드를 추가하는 것은 가능합니다. 하지만 이미 만들어진 필드를 삭제하거나 필드의 타입 및 설정값을 변경하는 것은 불가능 합니다. 필드의 변경이 필요한 경우 인덱스를 새로 정의하고 기존 인덱스의 값을 새 인덱스에 모두 재 색인 해야합니다.
인덱스에 데이터가 입력될 때 기존 매핑에 없는 필드값이라면 자동으로 추가가 됩니다.
문자열 - text, keyword
Elasticsearch에서 선언 가능한 문자열 타입에는 text, keyword 두 가지가 있습니다.
text
입력된 문자열을 텀 단위로 쪼개어 역 색인구조를 만듭니다. 일반적으로 풀텍스트 검색에 사용할 문자열 필드들을 text타입으로 지정합니다.
•
"analyzer" : "<애널라이저명>" - 색인에 사용할 애널라이저를 입력하며 디폴트로는 standard 애널라이저를 사용합니다. 토크나이저, 토큰필터들을 따로 지정할수가 없으며 필요하다면 사용자 정의 애널라이저를 settings에 정의 해 두고 사용합니다.
•
"search_analyzer" : "<애널라이저명>" - 기본적으로 text 필드는 match 쿼리로 검색을 할 때 색인에 사용한 동일한 애널라이저로 검색 쿼리를 분석합니다. search_analyzer 를 지정하면 검색시에는 색인에 사용한 애널라이저가 아닌 다른 애널라이저를 사용합니다. 보통 NGram 방식으로 색인을 했을 때는 지정 해 주는 것이 바람직합니다.
•
"index" : <true | false> - 디폴트는 true 입니다. false로 설정하면 해당 필드는 역 색인을 만들지 않아 검색이 불가능하게 됩니다.
•
"boost" : <숫자 값> - 디폴트는 1 입니다. 값이 1 보다 높으면 풀텍스트 검색 시 해당 필드 스코어 점수에 가중치를 부여합니다. 1보다 낮은 값을 입력하면 가중치가 내려갑니다.
•
"fielddata" : <true | false> - 디폴트는 false 입니다. true로 설정하면 해당 필드의 색인된 텀 들을 가지고 집계(aggregation) 또는 정렬(sorting)이 가능합니다. 이 설정은 다이나믹 설정으로 이미 정의된 매핑에 true 또는 false로 다시 적용하는 것이 가능합니다.
keyword
입력된 문자열을 하나의 토큰으로 지정합니다. text타입에 keyword 애널라이저를 적용한 것과 동일합니다. 일반적으로 집계(aggregation) 또는 정렬(sorting)에 사용할 문자열 필드를 keyword 타입으로 지정합니다.
•
index, boost 설정은 text 필드와 동일하게 동작합니다.
•
"doc_values" : <true | false> - 디폴트는 true 입니다. keyword 값들은 기본적으로 집계나 정렬에 메모리를 소모하지 않기 위해 값들을 doc_values 라고 하는 별도의 열 기반 저장소(columnar store)를 만들어 저장합니다. 이 값을 false로 하면 doc_values에 값을 저장하지 않아 집계나 정렬이 불가능해집니다.
•
"ignore_above" : <자연수> - 디폴트는 2,147,483,647 이며 다이나믹 매핑으로 생성되면 ignore_above: 256 로 설정이 됩니다. 설정된 길이 이상의 문자열은 색인을 하지 않아 검색이나 집계가 불가능합니다. _source에는 남아있기 때문에 다른 필드 값을 쿼리해서 나온 결과로 가져오는 것은 가능합니다.
•
"normalizer" : "<노멀라이저명>" - keyword 필드는 애널라이저를 사용하지 않는 대신 노멀라이저(normalizer) 의 적용이 가능합니다. 노멀라이저는 애널라이저와 유사하게 settings 에서 정의하며 토크나이저는 적용할 수 없고 캐릭터 필터와 토큰 필터만 적용해서 사용이 가능합니다.
PUT blogs
{
"settings": {
"analysis": {
"analyzer": {
"engram_a": {
"tokenizer": "standard",
"filter": [ "lowercase", "engram_f" ]
}
},
"filter": {
"engram_f": {
"type": "edge_ngram",
"min_gram": 2,
"max_gram": 5
}
},
"normalizer": {
"norm_low": {
"type": "custom",
"filter": [ "lowercase", "asciifolding" ]
}
}
}
},
"mappings": {
"properties": {
"title": {
"type": "text",
"boost": 2,
"fields": {
"keyword": {
"type": "keyword",
"normalizer": "norm_low"
}
}
},
"author": {
"type": "text",
"analyzer": "engram_a",
"search_analyzer": "standard",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"synopsis": {
"type": "text",
"fielddata": true
},
"category": {
"type": "keyword"
},
"content": {
"type": "text",
"index": false
}
}
}
}
JSON
복사
따라서, keyword 타입 데이터를 별도의 doc_values에 저장하는 것은 주로 성능 최적화와 리소스 관리를 위한 선택입니다. 이렇게 데이터를 구조화하여 저장하면, 필요한 작업을 수행할 때 더 빠르고 효율적인 데이터 접근이 가능해집니다.
숫자 - long, double …
•
long : 64비트 정수 (-9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807)
•
integer : 32비트 정수 (-2147483648 ~ 2147483647)
•
short : 16비트 정수 (-32768 ~ 32767)
•
byte : 8비트 정수 (-128 ~ 127)
•
double : 64비트 실수
•
float : 32비트 실수
•
half_float : 16비트 실수
•
scaled_float : 실수형이지만 부동소수점이 아니라 long 형태로 저장하고 옵션으로 소수점 위치를 지정합니다. 통화 (예: $19.99) 같이 소수점 자리가 고정된 값을 표시할 때 유용합니다.
모든 숫자 필드들에 공통적으로 설정 가능한 옵션들은 다음과 같은 것들이 있습니다.
•
"index", "doc_values", "boost" 옵션들은 text, keyword 필드의 옵션들과 동일합니다.
•
"coerce": <true | false> - 디폴트는 true 입니다. 숫자 필드들은 기본적으로 숫자로 이해될 수 있는 값들은 숫자로 변경해서 저장합니다. 예를 들어 integer 필드에 4, "4", 4.5 등을 입력하면 모두 자연수 4로 자동으로 변환되어 저장됩니다. false 로 설정하면 정확한 타입으로 입력되지 않으면 오류가 발생합니다.
•
"null_value" : <숫자값> - 필드값이 입력되지 않거나 null 인 경우 해당 필드의 디폴트 값을 지정합니다.
•
"ignore_malformed" : <true | false> - 디폴트는 false 입니다. 기본적으로 숫자 필드에 숫자가 아닌 문자나 불린 값이 들어오면 Elasticsearch는 오류를 리턴합니다. true로 설정하게 되면 숫자가 아닌 값이 들어와도 도큐먼트를 정상적으로 저장합니다. 하지만 해당 필드의 값은 _source 에만 저장되고 검색이나 집계에는 무시됩니다.
다음은 scaled_float 타입에서만 사용되는 옵션입니다.
•
"scaling_factor" : <10의 배수> - scaled_float 를 사용하려면 필수로 지정해야 하는 옵션입니다. 소수점 몇 자리까지 저장할지를 지정합니다. 12.3456 이라는 값을 저장하는 경우 scaling_factor: 10 으로 설정했으면 실제로는 12.3 이 저장됩니다. scaling_factor : 100 으로 설정했으면 12.34 가 저장됩니다.
날짜 - date
Elasticsearch에서 날짜 타입은 ISO8601형식을 따라 입력합니다.
•
"2019-06-12"
•
"2019-06-12T17:13:40"
•
"2019-06-12T17:13:40+09:00"
•
"2019-06-12T17:13:40.428Z"
이 외에도 1550282065513 와 같이 long 타입의 정수인 epoch_millis 형태의 입력도 가능합니다. epoch_millis 는 1970-01-01 00:00:00 부터의 시간을 밀리초 단위로 카운트 한 값입니다.
다른 형식으로 날짜를 저장하려면 format옵션을 사용해서 형태를 지정해야 합니다.
// my_date 인덱스 선언
PUT my_date
{
"mappings": {
"properties": {
"date_val": {
"type": "date",
"format": "yyyy-MM-dd HH:mm:ss||yyyy/MM/dd||epoch_millis"
}
}
}
}
JSON
복사
불리언 - boolean
•
"doc_values", "index" 옵션들은 문자열, 숫자 필드와 기능이 동일합니다.
•
"null_value" : <true | false> - 필드가 존재하지 않거나 값이 null 일 때 디폴트 값을 지정합니다. 지정하지 않으면 불리언 필드가 없거나 값이 null인 경우 존재하지 않는 것으로 처리되어 true / false 모두 쿼리나 집계에 나타나지 않습니다.
Object와 Nested
Object
JSON에서는 한 필드 안에 하위 필드를 넣는 object, 즉 객체 타입의 값을 사용할 수 있습니다. 보통은 한 요소가 여러 하위 정보를 가지고 있는 경우 object타입 형태로 사용합니다.
// object 타입 characters 필드를 가진 도큐먼트
PUT movie/_doc/1
{
"characters": {
"name": "Iron Man",
"age": 46,
"side": "superhero"
}
}
JSON
복사
// 매핑에 object 타입 characters 필드 선언
PUT movie
{
"mappings": {
"properties": {//
"characters": {
"properties": {//
"name": {
"type": "text"
},
"age": {
"type": "byte"
},
"side": {
"type": "keyword"
}
}
}
}
}
}
JSON
복사
object 필드를 쿼리로 검색하거나 집계를 할 때는 .을 이용해서 하위 필드에 접근합니다.
// characters 하위의 name 필드 쿼리
GET movie/_search
{
"query": {
"match": {
"characters.name": "Iron Man"
}
}
}
JSON
복사
Elasticsearch에는 따로 배열(array)타입의 필드를 선언하지 않습니다. 필드 타입의 값만 일치하면 다음과 같이 값을 배열로도 넣을 수 있습니다.
•
{ "title": "Romeo and Juliet" }
•
{ "title": [ "Romeo and Juliet", "Hamlet" ] }
필드에 object 값이 2개씩
// characters 필드에 2개의 ojbect 값들을 배열로 가진 도큐먼트 2개 입력
PUT movie/_doc/2
{
"title": "The Avengers",
"characters": [
{
"name": "Iron Man",
"side": "superhero"
},
{
"name": "Loki",
"side": "villain"
}
]
}
PUT movie/_doc/3
{
"title": "Avengers: Infinity War",
"characters": [
{
"name": "Loki",
"side": "superhero"
},
{
"name": "Thanos",
"side": "villain"
}
]
}
JSON
복사
// characters 하위 필드의 name: Loki, side: villain 검색
GET movie/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"characters.name": "Loki"
}
},
{
"match": {
"characters.side": "villain"
}
}
]
}
}
}
// 결과
{
"took" : 2,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 2,
"relation" : "eq"
},
"max_score" : 1.0611372,
"hits" : [
{
"_index" : "movie",
"_type" : "_doc",
"_id" : "3",
"_score" : 1.0611372,
"_source" : {
"title" : "Avengers: Infinity War",
"characters" : [
{
"name" : "Loki",
"side" : "superhero"
},
{
"name" : "Thanos",
"side" : "villain"
}
]
}
},
{
"_index" : "movie",
"_type" : "_doc",
"_id" : "2",
"_score" : 0.9827781,
"_source" : {
"title" : "The Avengers",
"characters" : [
{
"name" : "Iron Man",
"side" : "superhero"
},
{
"name" : "Loki",
"side" : "villain"
}
]
}
}
]
}
}
JSON
복사
원하는 결과가 나오지 않는 이유는 역 색인이 다음과 같은 모양으로 생성되기 때문입니다. 역 색인은 필드 별로 생성되는 것을 명심해야 합니다.
Nested
만약 object 타입 필드에 있는 여러개의 object 값들이 서로 다른 역 색인 구조를 갖도록 하려면 nested 타입으로 지정해야 합니다. 이는 인덱스를 매핑할때 "type": "nested" 를 명시합니다.
// 매핑에 nested 타입 characters 필드 선언
PUT movie
{
"mappings": {
"properties": {
"characters": {
"type": "nested",// 해당 부
"properties": {
"name": {
"type": "text"
},
"side": {
"type": "keyword"
}
}
}
}
}
}
JSON
복사
nested 필드를 검색 할 때는 반드시 nested 쿼리를 써야합니다. nested 쿼리 안에는 path라는 옵션으로 nested로 정의된 필드를 먼저 명시하고 그 안에 다시 쿼리를 넣어서 입력합니다.
// nested 쿼리로 characters 하위 필드의 name: Loki, side: villain 검색
GET movie/_search
{
"query": {// 해당
"nested": {// 부분
"path": "characters",// 필요
"query": {
"bool": {
"must": [
{
"match": {
"characters.name": "Loki"
}
},
{
"match": {
"characters.side": "villain"
}
}
]
}
}
}
}
}
JSON
복사
nested 쿼리로 검색하면 nested 필드의 내부에 있는 값 들을 모두 별개의 도큐먼트로 취급합니다.
위치 정보 - Geo
Geo Point
Geo Point는 위도(latitude)와 경도(longitude) 두 개의 실수 값을 가지고 지도 위의 한점을 나타내는 값입니다. 다양한 방식으로 입력이 가능합니다.
// object 형식으로 geo_point 입력
PUT my_locations/_doc/1
{
"location": {
"lat": 41.12,
"lon": -71.34
}
}
// text 형식으로 geo_point 입력
PUT my_index/_doc/2
{
"location": "41.12,-71.34"
}
// geohash 형식으로 geo_point 입력
PUT my_index/_doc/3
{
"location": "drm3btev3e86"
}
// 실수의 배열 형식으로 geo_point 입력
PUT my_index/_doc/4
{
"location": [
-71.34,
41.12
]
}
JSON
복사
일반적으로 object 형식으로 입력합니다.
기타 필드 타입 - IP, Range, Binary
IP
IP 주소 형식을 저장합니다. 매핑은 "type": "ip" 으로 선언합니다. 값은 "192.168.1.1" 같은 IPv4 형식과 "0:0:0:0:0:ffff:c0a8:105" 같은 IPv6 형식을 문자열 처럼 입력합니다.
범위(Range)
숫자나 날짜, IP 등을 시작과 끝이 있는 2차원의 범위 형태로 저장합니다. 매팽의 "type" 에 선언 가능한 값은 integer_range, float_range, long_range, double_range, date_range, ip_range 들이 있습니다. 데이터의 범위는 다음과 같이 gt, gte, lt, lte 를 사용해서 지정합니다.
// integer_range 와 date_range 타입의 필드 선언
PUT my_range
{
"mappings": {
"properties": {
"amount": {
"type": "integer_range"
},
"days": {
"type": "date_range"
}
}
}
}
JSON
복사
// integer_range, date_range 타입의 값을 가진 도큐먼트 입력
PUT my_range/_doc/1
{
"amount": {
"gte": 19,
"lt": 28
},
"days": {
"gt": "2019-06-01T09:00:00",
"lt": "2019-06-20"
}
}
JSON
복사
Range 필드의 쿼리는 일반적인 숫자나 날짜 처럼 range 쿼리를 사용합니다. 다만 범위 데이터를 range 쿼리로 검색 할 때는 추가로 relation 옵션의 값을 입력해야 하며 입력하지 않으면 오류가 납니다. relation 옵션에 지정 가능한 값은 within, contains, intersects 3가지가 있습니다.
•
within : 도큐먼트 범위 값이 쿼리한 범위 안에 완전히 포함되는 도큐먼트들을 가져옵니다.
•
contains : within과 반대로 쿼리 범위가 도큐먼트 범위 값 안에 완전히 포함되는 도큐먼트들을 가져옵니다.
•
Intersects : 도큐먼트 범위 값과 쿼리 범위에 공통적인 부분이 있는 도큐먼트들을 가져옵니다.
// "relation": "intersects" 으로 range 쿼리
GET my_range/_search
{
"query": {
"range": {
"amount": {
"gte": "16",
"lte": "25",
"relation": "intersects"
}
}
}
}
JSON
복사
Binary
"type": "binary" 로 지정해서 시스템 파일이나 이미지 정보 같은 바이너리 값을 저장할 수 있습니다. binary 필드는 기본적으로 색인이 되지 않아 검색이나 집계가 불가능하고 _source에만 남아 있습니다.
멀티 (다중) 필드 - Multi Field
Elasticsearch 의 도큐먼트에는 하나의 필드값만 있지만 이 필드의 값을 여러 개의 역 색인 및 doc_values 들로 저장할 수 있는 다중 필드, 즉 멀티 필드 기능이 있습니다.
보통은 text 타입 아래에 keyword 타입을 같이 정의하기 위해서 사용됩니다.
// 멀티 필드 설정
PUT my_index
{
"mappings": {
"properties": {
"<필드명1>": {
"type": "text",
"fields": {
"<필드명2>": {
"type": "<타입>"
}
}
}
}
}
}
JSON
복사
// english, nori_analyzer 를 사용하는 message 의 멀티필드 정의
PUT my_index
{
"settings": {
"analysis": {
"analyzer": {
"nori_analyzer": {
"tokenizer": "nori_tokenizer"
}
}
}
},
"mappings": {
"properties": {
"message": {
"type": "text",
"fields": {
"english": {
"type": "text",
"analyzer": "english"
},
"nori": {
"type": "text",
"analyzer": "nori_analyzer"
}
}
}
}
}
}
JSON
복사
위와 같이 매핑을 정의하면 도큐먼트에는 message 필드값만 있어도 message, message.english, messgae.nori 총 3개의 역 색인이 생성됩니다.
멀티 필드는 한 필드에 여러 애널라이저를 적용해야 하는 경우, 특히 다국어로 씌여진 도큐먼트를 분석해야할 때 유용합니다.