[ES] 4. 인덱스 매핑 하기 (동적 매핑, 데이터 타입)

mallin·2022년 3월 15일
0

ElasticSearch

목록 보기
4/9
post-thumbnail

이번에는 인덱스를 매핑하는 방법에 대해서 알아보겠습니다. 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" : <truefalse>
"boost" : <숫자 값>풀 텍스트 검색 시 해당 필드 스코어 점수에 가중치를 부여, default = 1
"fielddata" : <truefalse>

keyword

  • 입력된 문자열을 하나의 토큰으로 저장
  • text 타입에 keyword 애널라이저를 적용한 것과 동일
  • 집계 또는 정렬에 사용
옵션설명
index, boosttext 필드와 동일하게 동작
"doc_values" : <truefalse>
"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

숫자명범위
long64비트 정수
integer32비트 정수
short16비트 정수
byte8비트 정수
double64비트 실수
float32비트 실수
half_float16비트 실수
scaled_float실수형이지만, 부동소수점이 아니라 long 형태로 저장하고 옵션으로 소수점 위치를 지정

옵션설명
index, doc_values, boosttext, 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 인 도큐먼트가 조회되는 걸 확인할 수 있습니다.

날짜

  • ISO8601 형식을 따라 입력
  • "2019-06-02", "2019-06-12T17:13:40", "2019-06-12T17:13:40+09.00" 으로 입력된 경우 자동으로 날짜 타입으로 인식
  • 위와 같은 형식이 아닌 경우에는 text, keyword 로 저장
옵션설명
"format" : "<문자열 || 문자열 ...>"입력 가능한 날짜 형식을 ||로 구분해서 입력

EX)

PUT date_test
{
  "mappings": {
    "properties": {
      "date_format": {
        "type": "date",
        "format": "yyyy-MM-dd HH:mm:ss||yyyy/MM/dd"
      }
    }
  }
}

이런 식으로 사용할 수 있습니다.

불리언

true, false

  • "type": "boolean"으로 선언
  • 일반적으로 term 쿼리를 이용해서 검색

Object

객체 타입

  • 한 요소가 여러 하위 정보를 가지고 있는 경우 object 타입 형태로 사용
  • object 필드를 선언할 때는 "properties"를 입력하고 그 아래에 하위 필드 이름과 타입을 지정
  • object 필드를 쿼리로 검색 하거나 집계를 할 때는 마침표(.)를 이용해서 하위 필드에 접근
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
soyeon1,2
test1
chae1

user.phone

ID
010123412341,2
010151555551
010191919192

그렇기 때문에 soyeon, 01012341234 로 검색하는 경우 1과 2 도큐먼트를 모두 리턴합니다.

Nested

object 와 같은 문제가 일어나지 않게 하기 위해서는 nested 타입을 사용해야 합니다.

  • object 타입 필드에 있는 여러 개의 object 값들이 서로 다른 역 색인 구조를 갖도록 하는 타입
  • "type": "nested" 를 명시
  • nested 쿼리로 검색하면 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

Geo Point, Geo Shape

Geo Point

  • 위도(latitude)와 경도(longitude) 두 개의 실수 값을 가지고 지도 위의 한 점을 나타내는 값
  • 매핑을 정의하지 않으면 다이나믹 매핑으로 필드가 자동으로 생성되기 때문에 반드시 인덱스 매핑을 정의해줘야 함
PUT test_geo
{
  "mappings": {
    "properties": {
      "location": {
        "type": "geo_point"
      }
    }
  }
}

geo_bounding_box 쿼리

  • top_left 와 bottom_right 두 개의 옵션에 각각 위치점을 입력하고 이 점들을 토대로 그린 네모 칸 안에 위치하는 도큐먼트들을 불러옴
{
  "query": {
    "geo_bounding_box": {
      "location": {
        "bottom_right": {
          "lat": 37.4899,
          "lon": 127.0388
        },
        "top_left": {
          "lat": 37.5779,
          "lon": 126.9617
        }
      }
    }
  }
}

geo_distance 쿼리

  • 하나의 위치점을 찍고 distance 옵션을 이용해서 입력한 반경의 원 안에 있는 도큐먼트들을 불러옴
GET test_geo/_search
{
  "query": {
    "geo_distance": {
      "distance": "5km",
      "location": {
        "lat": 37.5358,
        "lon": 126.9559
      }
    }
  }
}

Geo Shape

  • 선, 면 등의 2차원 값을 저장하고 쿼리할 수 있음
  • "type": "geo_shape" 로 선언

geo_shape 쿼리
"type", "coordinates" : 검색할 영역
"relation": 검색할 영역과 검색되는 도큐먼트가 겹치거나 포함되는 관계 조건 값을 입력

PUT my_shape/_doc/1
{
  "location": {
    "type": "point",
    "coordinates": [
      127.027926,
      37.497175
    ]
  }
}

IP, Range, Binary

IP

  • IP 주소 형식을 저장
  • "type": "ip" 로 매핑

Range

  • 숫자나 날짜, IP 등을 시작과 끝이 있는 2차원의 범위 형태로 저장

Binary

  • 시스템 파일이나 이미지 정보 같은 바이너리 값을 저장
  • 기본적으로 색인이 되지 않아 검색이나 집계가 불가능하고, _source에만 남아 있음

레퍼런스

7.2 매핑 - Mappings

0개의 댓글