이번에는 인덱스를 매핑하는 방법에 대해서 알아보겠습니다. ES 같은 경우는 동적 매핑으로 매핑이 되는데요.
미리 정의하지 않아도 인덱스에 도큐먼트를 새로 추가하면 자동으로 매핑이 생성
example 인덱스가 없는 상태에서 example 인덱스 데이터를 추가하면
curl -XPUT 'localhost:9200/example/_doc/1?pretty' -H 'Content-type:application/json' -d '{"content":"test"}'
자동으로 example 인덱스가 생성되고, 매핑됩니다.
ES 의 매핑은 동적으로 생성될 때 필드의 값을 보고 타입을 예상하는데 항상 그 필드가 포함될 수 있는 가장 넓은 범위 형태의 데이터 타입을 선택합니다.
동적 매핑 방식으로 가장 넓은 범위 형태의 데이터 타입으로 매핑을 생성하는게 아니라 미리 정의 해 놓고 싶다면 아래와 같은 방식을 사용하면 됩니다.
PUT <인덱스명>
{
"mappings": {
"properties": {
"<필드명>":{
"type": "<필드 타입>"
… <필드 설정>
}
…
}
}
}
🚨 주의할 점
text, keyword
text
옵션 | 설명 |
---|---|
"analyzer" : "<애널라이저명>" | 색인에 사용할 애널라이저 |
"search_analyzer" : "<애널라이저명>" | 검색시 색인에 사용한 애널라이저가 아니라 다른 애널라이저를 사용해 검색 |
"index" : <true | false> |
"boost" : <숫자 값> | 풀 텍스트 검색 시 해당 필드 스코어 점수에 가중치를 부여, default = 1 |
"fielddata" : <true | false> |
keyword
옵션 | 설명 |
---|---|
index, boost | text 필드와 동일하게 동작 |
"doc_values" : <true | false> |
"ignore_above" : <자연수> | 저장할 길이 |
"normalizer" : "<노멀라이저명>" | 애널라이저와 유사, 캐릭터 필터와 토큰 필터 적용 가능 |
text vs keyword
설명만 봐서는 두개의 차이를 알 수 없기 때문에 실제로 인덱스를 만들어서 테스트를 해보겠습니다.
① index 만들기
curl -XPUT 'localhost:9200/test_index' -H 'Content-type:application/json' -d '{ "mappings": { "properties": {"text_name":{ "type": "text"},"keyword_name": {"type":"keyword"}}}}'
를 실행해서 type 이 keyword 인 keyword_name 과 type 이 text 인 text_name 을 가진 test_index 를 만들어줍니다.
curl -XGET 'localhost:9200/test_index/'
를 실행하면 정상적으로 index 가 만들어진 걸 확인할 수 있습니다.
② 데이터 넣기
curl -XPUT 'localhost:9200/test_index/_doc/1?pretty' -H 'Content-type:application/json' -d '{"text_name":"I am soyeon.", "keyword_name":"I am soyeon."}'
그리고 I am soyeon.
이라는 문자열을 text_name 과 keyword_name 에 넣어주겠습니다.
③ 데이터 조회하기
조회는 QueryDSL 을 통해서 조회하도록 하겠습니다.
curl -XGET 'localhost:9200/test_index/_search?pretty' -H 'Content-Type: application/json' -d @query.json
로 조회해주면 되고 query.json 파일에 조회 쿼리인 QueryDSL 을 넣어주면 됩니다.
먼저, keyword 에 soyeon 을 넣어서 조회해보면
{
"query": {
"match": {"keyword_name": "soyeon"}
}
}
데이터가 조회되지 않지만, text 타입에 soyeon 을 넣어서 조회해보면
데이터가 정상적으로 조회되는 걸 확인 할 수 있습니다.
이렇게 차이가 발생하는 이유는 위에 잠깐 소개 되어 있던 저장하는 방식 차이입니다.
text 는 입력된 문자열을 텀 단위로 쪼개어 역 색인 구조로 저장하지만, keyword 는 입력된 문자열을 통째로 저장하기 때문입니다.
🧐 엥 ? 그러면 keyword 타입은 어디에 쓰나요 ???
keyword 타입은 집계나 정렬에 사용하고 있습니다. text 도 물론 집계나 정렬에 사용할 수 있지만 keyword 에 비교해 더욱 많은 메모리를 차지하게 됩니다.
long, integer, short, byte, double, float, half_float, scaled_float
숫자명 | 범위 |
---|---|
long | 64비트 정수 |
integer | 32비트 정수 |
short | 16비트 정수 |
byte | 8비트 정수 |
double | 64비트 실수 |
float | 32비트 실수 |
half_float | 16비트 실수 |
scaled_float | 실수형이지만, 부동소수점이 아니라 long 형태로 저장하고 옵션으로 소수점 위치를 지정 |
옵션 | 설명 |
---|---|
index, doc_values, boost | text, keyword 필드의 옵션들과 동일 |
"coerce": true or false | 숫자로 이해될 수 있는 값들은 숫자로 변경해서 저장, default = true EX) "4", 4.5 입력시 4로 저장 |
"null_value" : <숫자값> | 필드값이 입력되지 않거나 null인 경우 해당 필드의 디폴트 값 |
"ignore_malformed" : true or false | 숫자 필드에 숫자가 들어오지 않았을 때 무시 여부 |
half_float, scaled_float 는 ES 에만 존재하는 데이터 타입입니다.
여기서 주목해서 봐야할 건 옵션의 coerce 인데요.
coerce 는 default 가 true 인데 4.5 를 저장해도 내부적으로 4가 저장되고, 4.3 을 저장해도 내부적으로는 4가 저장되기 때문에 검색이나 집계를 할 때 오류가 발생하게 됩니다 !!
말로만 해서는 잘 이해가 안되기 때문에 직접 실습해보도록 하겠습니다.
① text_num 인덱스 생성
curl -XPUT 'localhost:9200/test_num' -H 'Content-type:application/json' -d '{ "mappings": { "properties": {"long_num":{ "type": "long", "coerce": true}}}}'
type = long, coerce = true 인 long_num 을 property 로 가지고 있는 text_num 이라는 인덱스를 생성해줍니다.
② 데이터 넣어주기
curl -XPUT 'localhost:9200/test_num/_doc/1?pretty' -H 'Content-type:application/json' -d '{"long_num":4.3}'
long_num 에 4.3 을 넣어줍니다.
③ QueryDSL 을 사용해서 검색하기
query.json
{
"query": {
"range": { "long_num":{"gte":3, "lt":4.2}}
}
}
curl -XGET 'localhost:9200/test_num/_search?pretty' -H 'Content-Type: application/json' -d @query.json
3 <= long_num < 4.2 인 도큐먼트를 검색해줍니다.
저희가 넣어준 값은 4.3 이기 때문에 검색이 되지 않는데 맞는데요.
이게 웬걸 ? coerce 라 true 라 long_num 이 4로 저장되어서 long_num 이 4.3 인 도큐먼트가 조회되는 걸 확인할 수 있습니다.
옵션 | 설명 |
---|---|
"format" : "<문자열 || 문자열 ...>" | 입력 가능한 날짜 형식을 ||로 구분해서 입력 |
EX)
PUT date_test
{
"mappings": {
"properties": {
"date_format": {
"type": "date",
"format": "yyyy-MM-dd HH:mm:ss||yyyy/MM/dd"
}
}
}
}
이런 식으로 사용할 수 있습니다.
true, false
객체 타입
PUT game
{
"mappings": {
"properties": {
"user": {
"properties": {
"name": {
"type": "text"
},
"age": {
"type": "byte"
},
"phone": {
"type": "keyword"
}
}
}
}
}
}
id = 1
"user" : [
{"name" : "soyeon","age" : 21,"phone" : "01012341234"},
{"name" : "test","age" : 15,"phone" : "01015155555"}
]
id = 2
"user" : [
{"name" : "chae","age" : 30,"phone" : "01012341234"},
{"name" : "soyeon","age" : 19,"phone" : "01019191919"}
]
로 저장되어 있을 때
query.json
{
"query": {
"bool": {
"must": [
{
"match": {
"user.name": "soyeon"
}
},
{
"match": {
"user.phone": "01012341234"
}
}
]
}
}
}
curl -XGET 'localhost:9200/test_game/_search?pretty' -H 'Content-Type: application/json' -d @query.json
를 실행하면 user.name 이 soyeon 이고, user.phone 이 01012341234 인 도큐먼트만 검색이 되겠지 ? 라고 기대하지만 ...
두개의 도큐먼트 모두 검색되는 걸 확인할 수 있습니다. 이유는 바로 역색인 때문인데요.
user.name
텀 | ID |
---|---|
soyeon | 1,2 |
test | 1 |
chae | 1 |
user.phone
텀 | ID |
---|---|
01012341234 | 1,2 |
01015155555 | 1 |
01019191919 | 2 |
그렇기 때문에 soyeon, 01012341234 로 검색하는 경우 1과 2 도큐먼트를 모두 리턴합니다.
object 와 같은 문제가 일어나지 않게 하기 위해서는 nested 타입을 사용해야 합니다.
위의 예시를 Nested 타입을 사용해서 쿼리해보도록 하겠습니다.
curl -XPUT 'localhost:9200/test_game_nested' -H 'Content-type:application/json' -d '{"mappings": {"properties": {"user": {"type": "nested", "properties": {"name": {"type": "text"},"age": {"type": "byte"},"phone": {"type": "keyword"}}}}}}'
"type" 을 nested 로 지정해서 test_game_nested 인덱스를 만들어주고, 조회하면
query.json
{
"query": {
"nested": {
"path": "user",
"query": {
"bool": {
"must": [{
"match": {"user.name": "soyeon"}
},
{
"match": {"user.phone": "01012341234"}
}]
}
}
}
}
curl -XGET 'localhost:9200/test_game_nested/_search?pretty' -H 'Content-Type: application/json' -d @query.json
object 별로 역인덱싱 되어 데이터가 하나만 나오게 되는 걸 알 수 있습니다.
Geo Point, Geo Shape
PUT test_geo
{
"mappings": {
"properties": {
"location": {
"type": "geo_point"
}
}
}
}
geo_bounding_box 쿼리
{
"query": {
"geo_bounding_box": {
"location": {
"bottom_right": {
"lat": 37.4899,
"lon": 127.0388
},
"top_left": {
"lat": 37.5779,
"lon": 126.9617
}
}
}
}
}
geo_distance 쿼리
GET test_geo/_search
{
"query": {
"geo_distance": {
"distance": "5km",
"location": {
"lat": 37.5358,
"lon": 126.9559
}
}
}
}
geo_shape 쿼리
"type", "coordinates" : 검색할 영역
"relation": 검색할 영역과 검색되는 도큐먼트가 겹치거나 포함되는 관계 조건 값을 입력
PUT my_shape/_doc/1
{
"location": {
"type": "point",
"coordinates": [
127.027926,
37.497175
]
}
}
"type": "ip"
로 매핑