날짜 - Date
Elasticsearch 에서 날짜 타입은 ISO8601 형식을 따라 입력을 합니다. 일반적으로 다음과 같은 형태로 입력된 경우 자동으로 날짜 타입으로 인식이 됩니다.
위와 같은 ISO8601 형식이 아니라 "2019/06/12 12:10:30" 와 같이 입력하면 보통은 text, keyword 로 저장됩니다. 이 외에도 1550282065513 와 같이 long 타입의 정수인 epoch_millis 형태의 입력도 가능합니다. epoch_millis 는 1970-01-01 00:00:00 부터의 시간을 밀리초 단위로 카운트 한 값입니다.
필드가 date 형으로 정의된 이후에는 long 타입의 정수를 입력하면 날짜 형태로 저장이 가능합니다. "2019/06/10 12:10:30" 같은 형식으로 날짜를 저장하려면 format 옵션을 사용해서 형태를 지정해야 합니다.
다음은 my_date 인덱스에서 "2019/06/10", "2019-06-10 12:10:30" 그리고 epoch_millis 형태로 입력받도록 date_val 날짜 필드를 지정하는 예제입니다.
PUT my_date
{
"mappings": {
"properties": {
"date_val": {
"type": "date",
"format": "yyyy-MM-dd HH:mm:ss||yyyy/MM/dd||epoch_millis"
}
}
}
}
정의된 포맷들은 Elastic 홈페이지의 공식 도큐먼트 https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-date-format.html#built-in-date-formats
페이지에서 볼 수 있습니다.
날짜 필드는 입력된 값들을 실제로 내부에서는 모두 long 형태의 epoch_millis 로 저장합니다. 또한 매핑의 format 형식만 지정해 놓으면 지정된 어떤 형식으로도 색인 및 쿼리가 가능합니다.
다시 말해 _source 의 날짜는"2019-09-12" 형식으로 입력 되었어도 "2019/09/12" 형식으로 range 쿼리를 해도 정상적으로 동작합니다.
# yyyy/MM/dd, epoch_millis 형식으로 날짜 검색
GET my_date/_search
{
"query": {
"range": {
"date_val": {
"gt": "2019/09/10",
"lt": 1568332800000
}
}
}
}
입력된 포맷과 검색 쿼리 포맷이 달라도 정상적으로 검색되는 것을 확인할 수 있습니다.
Object 와 Nested
Object
JSON 에서는 한 필드 안에 하위 필드를 넣는 object, 즉 객체 타입의 값을 사용할 수 있습니다. 보통은 한 요소가 여러 하위 정보를 가지고 있는 경우 object 타입 형태로 사용합니다.
# Object 타입 characters 필드를 가진 도큐먼트
PUT movie/_doc/1
{
"characters": {
"name": "Iron Man",
"age": 46,
"side": "superhero"
}
}
object 필드를 선언할 때는 다음과 같이 "properties" 를 입력하고 그 아래에 하위 필드 이름과 타입을 지정합니다.
PUT movie
{
"mappings": {
"properties": {
"characters": {
"properties": {
"name": {
"type": "text"
},
"age": {
"type": "byte"
},
"side": {
"type": "keyword"
}
}
}
}
}
}
object 필드를 쿼리로 검색 하거나 집계를 할 때는 다음과 같이 마침표 . 를 이용해서 하위 필드에 접근합니다.
GET movie/_search
{
"query": {
"match": {
"characters.name": "Iron Man"
}
}
}
이번에 characters 필드에 2개의 object 값들을 배열로 가진 도큐먼트 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"
}
]
}
characters 필드의 name 값은 "Loki" 이고 side 값은 "villain" 인 도큐먼트를 검색해 보겠습니다.
GET movie/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"characters.name": "Loki"
}
},
{
"match": {
"characters.side": "villain"
}
}
]
}
}
}
분명 {"name": "Loki", "side": "villain"} 값을 포함하고 있는 도큐먼트는 "_id" : "2" 인 "title": "The Avengers" 도큐먼트뿐인데 {"name": "Loki", "side": "superhero"} 를 포함하고 있는 "title": "Avengers: Infinity War" 도큐먼트도 같이 검색이 되었습니다. (심지어 스코어도 더 높습니다)
이유는 Elasticsearch는 위 예제에서 역 색인을 다음과 같은 모양으로 생성하기 때문입니다. 역 색인은 필드 별로 생성됩니다.
역 색인에서는 object 필드의 하위 필드들은 모두 상위 필드의 이름과 함께 펼쳐져서 한 필드로 저장이 됩니다. 그래서 두 도큐먼트 모두를 리턴합니다.
Nested
만약에 object 타입 필드에 있는 여러 개의 object 값들이 서로 다른 역 색인 구조를 갖도록 하려면 nested 타입으로 지정해야 합니다. nested 타입으로 지정하려면 매핑에 다음과 같이 "type": "nested" 를 명시합니다. 다른 부분은 object 와 동일합니다.
PUT movie
{
"mappings": {
"properties": {
"characters": {
"type": "nested",
"properties": {
"name": {
"type": "text"
},
"side": {
"type": "keyword"
}
}
}
}
}
}
nested 필드를 검색할 때는 반드시 nested 쿼리를 써야 합니다. nested 쿼리 안에는 path 라는 옵션으로 nested로 정의된 필드를 먼저 명시하고 그 안에 다시 쿼리를 넣어서 입력합니다.
GET movie/_search
{
"query": {
"nested": {
"path": "characters",
"query": {
"bool": {
"must": [
{
"match": {
"characters.name": "Loki"
}
},
{
"match": {
"characters.side": "villain"
}
}
]
}
}
}
}
}
결과로 {"name" : "Loki", "side" : "villain"} 값을 포함하고 있는 "_id" : "2" 도큐먼트만 검색이 되었습니다.
nested 쿼리로 검색하면 nested 필드의 내부에 있는 값 들을 모두 별개의 도큐먼트로 취급합니다. 앞의 예제에서 본 object 도큐먼트와 nested 도큐먼트를 그림으로 비교해 보면 다음과 같습니다.
object 필드 값들은 실제로 하나의 도큐먼트 안에 전부 포함되어 있습니다. 반면에 nested 필드 값들은 내부적으로 별도의 도큐먼트로 분리되어 저장되며 쿼리 결과에서 상위 도큐먼트와 합쳐져서 보이게 됩니다.
위치 정보 - Geo
Geo Point
Geo Point 는 위도(latitude)와 경도(longitude) 두 개의 실수 값을 가지고 지도 위의 한 점을 나타내는 값입니다. Geo Point 필드의 값들은 다음과 같이 다양한 방법으로 입력이 가능합니다.
# 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
]
}
보통은 {"lat": 41.12, "lon": -71.34} 와 같이 알아보기 편한 object 형식으로 입력합니다.
Geo Point 필드는 매핑에서 다음과 같이 "type": "geo_point" 로 선언합니다.
PUT my_geo
{
"mappings": {
"properties": {
"location": {
"type": "geo_point"
}
}
}
}
Geo Point 필드의 경우는 반드시 데이터를 입력하기 전에 인덱스 매핑을 정의해 주어야 합니다. 매핑을 정의하지 않고 값을 입력하면 다이나믹 매핑으로 필드가 자동 생성될 때 float 타입의 lat, lon 두 개의 하위 필드가 생깁니다.
geo_bounding_box 쿼리
Elasticsearch에는 위치정보를 검색할 수 있는 다양한 쿼리들이 있습니다. geo_point 값의 검색에 주로 사용되는 것은 geo_bounding_box 쿼리와 geo_distance 쿼리입니다.
my_geo 인덱스에 예제 데이터 bulk 입력하겠습니다.
PUT my_geo/_bulk
{"index":{"_id":"1"}}
{"station":"강남","location":{"lon":127.027926,"lat":37.497175},"line":"2호선"}
{"index":{"_id":"2"}}
{"station":"종로3가","location":{"lon":126.991806,"lat":37.571607},"line":"3호선"}
{"index":{"_id":"3"}}
{"station":"여의도","location":{"lon":126.924191,"lat":37.521624},"line":"5호선"}
{"index":{"_id":"4"}}
{"station":"서울역","location":{"lon":126.972559,"lat":37.554648},"line":"1호선"}
geo_bounding_box 쿼리는 top_left 와 bottom_right 두 개의 옵션에 각각 위치점을 입력하고 이 점들을 토대로 그린 네모 칸 안에 위치하는 도큐먼트들을 불러옵니다.
GET my_geo/_search
{
"query": {
"geo_bounding_box": {
"location": {
"bottom_right": {
"lat": 37.4899,
"lon": 127.0388
},
"top_left": {
"lat": 37.5779,
"lon": 126.9617
}
}
}
}
}
geo_distance 쿼리
geo_distance 쿼리는 하나의 위치점을 찍고 distance 옵션을 이용해서 입력한 반경의 원 안에 있는 도큐먼트들을 불러옵니다.
GET my_geo/_search
{
"query": {
"geo_distance": {
"distance": "5km",
"location": {
"lat": 37.5358,
"lon": 126.9559
}
}
}
}
Geo shape
앞에서 살펴본 Geo Point 는 위도 경도 두 개의 값을 가진 1차원 데이터 "점" 입니다.
Elasticsearch 에서 사용 가능한 또 다른 위치정보 타입인 Geo Shape 은 선, 면 등의 2차원 값을 저장하고 쿼리 할 수 있습니다.
# geo_shape 필드 선언
PUT my_shape
{
"mappings": {
"properties": {
"location": {
"type": "geo_shape"
}
}
}
}
도큐먼트에도 점, 선, 다중점, 다중선, 다각형 등을 "type" 값에 각각 다음과 같이 지정하고 "coordinates" 값에 위치 정보를 [ -71.34, 41.12 ] 같이 [경도, 위도] 의 순서로 배열 형식으로 입력합니다. 위치정보 순서가 반대로 되면 엉뚱한 값이 되기 때문에 주의해야 합니다.
대표적인 type으로 "point", "linestring", "polygon", "envelope"에 대해 그려보겠습니다. 이외에도 다양한 type이 존재하니 공식 문서에 다양하게 나와있습니다. (앞에 multi를 붙여 2개 이상의 위치에 대해 처리도 가능합니다.)
- "type": "point" - 단일 점입니다. 보통은 geo_point 와 같은 용도로 사용됩니다.
PUT my_shape/_doc/1
{
"location": {
"type": "point",
"coordinates": [
127.027926,
37.497175
]
}
}
- "type": "linestring" - 점 2개 값을 배열로 입력하여 두 점을 잇는 직선을 저장합니다. 비행경로 등을 저장할 때 유용합니다.
PUT my_shape/_doc/3
{
"location": {
"type": "linestring",
"coordinates": [
[ 127.027926, 37.497175 ],
[ 126.991806, 37.571607 ]
]
}
}
- "type": "polygon" - 다각형을 저장합니다. 내부에 배열을 추가하고 점들을 배열로 입력하며 순서대로 이어집니다. 배열 마지막에는 반드시 처음과 같은 점이 입력되어야 합니다. 영토 정보 등을 저장할때 유용합니다.
PUT my_shape/_doc/5
{
"location": {
"type": "polygon",
"coordinates": [
[
[ 127.027926, 37.497175 ],
[ 126.991806, 37.571607 ],
[ 126.924191, 37.521624 ],
[ 126.972559, 37.554648 ],
[ 127.027926, 37.497175 ]
]
]
}
}
- "type": "envelope" - 바른 각도의 직사각형 영역을 저장할 때는 polygon 으로 4개의 점을 입력하여 지정할 수도 있지만, 대신 envelope 을 사용하면 좌측 상단(upper left) 와 우측 하단(lower right) 점 두 개만 이용해서 그리는 것도 가능합니다.
PUT my_shape/_doc/7
{
"location": {
"type": "envelope",
"coordinates": [
[ 126.936893, 37.555134 ],
[ 127.004943, 37.50481 ]
]
}
}
geo_shape 쿼리
Geo Shape 타입의 값들을 검색하려면 geo_shape 쿼리를 사용해야 합니다.
입력해야 하는 옵션으로는 "shape": { } 에 검색할 영역의 "type" 과 "coordinates" 값을 입력하고, "relation" 에 검색할 영역과 검색되는 도큐먼트가 겹치거나 포함되는 관계 조건 값을 입력합니다.
relation 옵션에 입력 가능한 값은 intersects, disjoint, within 3가지가 있습니다.
- "relation": "intersects" - 디폴트 값입니다. 쿼리 영역과 도큐먼트 값 영역이 일부라도 겹쳐지면 참입니다.
- "relation": "disjoint" - 도큐먼트 값 영역이 쿼리 영역과 겹치지 않는 쿼리 영역 바깥에 있는 도큐먼트들을 가져옵니다.
- "relation": "within" - 도큐먼트의 값들이 모두 쿼리 영역 안에 완전히 포함되어 있는 도큐먼트들을 가져옵니다.
예제 실행을 위해 my_shape 인덱스에 다음 세 개의 envelope 방식의 geo_shape 값을 입력하겠습니다. 먼저 location 필드의 매핑을 "type": "geo_shape" 으로 지정해야 하는 것을 명심하세요.
PUT my_shape/_doc/1
{
"place": "경복궁",
"location": {
"type": "envelope",
"coordinates": [
[ 126.9735, 37.5837 ],
[ 126.9802, 37.5756 ]
]
}
}
PUT my_shape/_doc/2
{
"place": "명동",
"location": {
"type": "envelope",
"coordinates": [
[ 126.9778, 37.5656 ],
[ 126.9884, 37.5558 ]
]
}
}
PUT my_shape/_doc/3
{
"place": "홍대",
"location": {
"type": "envelope",
"coordinates": [
[ 126.9199, 37.5583 ],
[ 126.9347, 37.5481 ]
]
}
}
다음은 geo_shape 쿼리의 relation 값을 각각 intersects, within, disjoint 한 결과입니다.
GET my_shape/_search
{
"query": {
"geo_shape": {
"location": {
"shape": {
"type": "envelope",
"coordinates": [
[ 126.9687, 37.58 ],
[ 126.99, 37.5543 ]
]
},
"relation": "intersects" # "relation": "within", "relation": "disjoint"
}
}
}
}
이 외에도 정사각형이 아닌 다각형으로 쿼리 할 수 있는 geo_polygon 쿼리가 있습니다. 해당 포스트에는 다루지 않으니 공식 문서를 확인하시기 바랍니다.
https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-geo-polygon-query.html
마무리
이번 포스팅에서 elasticsearch의 Date 타입 매핑과 Object, Nested 타입 각각의 사용 용도를 알아보았습니다. 또, 위치 정보를 나타내는 geo 타입에 대해서도 알아보았습니다.
다음 포스팅은 데이터 수집과 가공 기능을 제공하는 logstash와 logstash filter에 대해 알아보겠습니다.
감사합니다.
'ELK stack' 카테고리의 다른 글
[ELK] 9. filebeat - logstash - elasticsearch (0) | 2022.06.30 |
---|---|
[ELK] 8. Logstash 실행 및 filter 설정 (0) | 2022.06.29 |
[ELK] 6. elasticsearch 인덱스 매핑과 데이터 타입(1) - 문자열, 숫자 (0) | 2022.06.28 |
[ELK] 5. elasticsearch 노리 한글 형태소 분석기 (0) | 2022.06.27 |
[ELK] 4. elasticsearch 데이터 색인과 텍스트 분석 (0) | 2022.06.26 |
댓글