Relation
관계형 DB 에는 엔티티간에 키를 설정하여 연결을 설정한다.
예를 들어 특정 부서가 어떤 도시에 속해있는지를 관계형 DB 로 표현하면 아래와 같이 스키마를 만들고
데이터는 아래와 같이 각 부서별 도시를 포인팅하는 방식으로 저장 공간이 효율적으로 사용되며
데이터를 읽기 위해서는 두 Table 을 Join Query 하여 그 결과를 얻을수 있다.
Denormalized Database
NoSQL 은 기본적으로 RDBMS 방식의 관계 (엔티티를 키로 연결하는 것) 를 지원하지 않는다.
따라서 관계를 비정규화 하는 방식으로 데이터를 저장한다.
예를 들어 아래의 category 를 비정규화 하면 User_messages 에 category 이름을 직접 저장함으로써
User_messages 테이블에서 카테고리 명을 직접 검색할 수 있게 된다.
Nested Field
Elasticsearch 는 NoSQL 이기 때문에
위에서 설명한 반정규화 / 중첩 객체 방식으로 데이터를 저장하는 것을 권장하고 있다.
결국 관계를 비정규화 하면 다른 테이블의 정보를 object 로 저장하게 되며
이를 nested field ( 중첩필드 ) 라고 한다.
이는 아래와 같이 기존 document 에 다른 document 의 정보중
필요한 정보를 object 로 중첩 저장하는 것이다.
앞으로의 테스트를 위해 아래와 같이 index 를 만들어 저장해 보자.
PUT /department
{
"mappings":{
"_doc":{
"properties" :{
"name":{
"type":"text"
},
"employees":{
"type":"nested"
}
}
}
}
}
POST /department/_doc/1
{
"name":"Devlopment",
"employees": [
{
"name":"Eric Green",
"age":39,
"gender":"M",
"position":"Big Data Specialist"
},
{
"name":"James Taylor",
"age":27,
"gender":"M",
"position":"Software Developer"
},
{
"name":"Gray Jenkins",
"age":21,
"gender":"M",
"position":"Intern"
},
{
"name":"Julie Powell",
"age":26,
"gender":"F",
"position":"Intern"
},
{
"name":"Benjamin Smith",
"age":46,
"gender":"M",
"position":"Senior Software Engineer"
}
]
}
|
Query nested objects
중첩 필드에 속해있는 데이터를 검색하기 위해서는 쿼리시
nested 옵션 사용해야 하며 nested.path 에 검색할 object 를 지정하면 된다.
GET /department/_search
{
"query":{
"nested": {
"path": "employees",
"query": {
"bool":{
"must":[
{
"match":{
"employees.position": "intern"
}
}
],
"filter":[
{
"term":{
"employees.gender.keyword": {
"value":"F"
}
}
}
]
}
}
}
}
}
|
{
"took" : 4,
"timed_out" : false,
"_shards" : {
"total" : 5,
"successful" : 5,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : 1,
"max_score" : 1.1005893,
"hits" : [
{
"_index" : "department",
"_type" : "_doc",
"_id" : "1",
"_score" : 1.1005893,
"_source" : {
"name" : "Devlopment",
"employees" : [
{
"name" : "Eric Green",
"age" : 39,
"gender" : "M",
"position" : "Big Data Specialist"
},
{
"name" : "James Taylor",
"age" : 27,
"gender" : "M",
"position" : "Software Developer"
},
{
"name" : "Gray Jenkins",
"age" : 21,
"gender" : "M",
"position" : "Intern"
},
{
"name" : "Julie Powell",
"age" : 26,
"gender" : "F",
"position" : "Intern"
},
{
"name" : "Benjamin Smith",
"age" : 46,
"gender" : "M",
"position" : "Senior Software Engineer"
}
]
}
}
]
}
}
|
Nested inner hits
위 검색의 결과에는 employees 중 match 된 documents 만 보기가 힘들다.
이런 경우 inner hits 옵션을 사용한다.
GET /department/_search
{
"query":{
"nested": {
"path": "employees",
"inner_hits":{},
"query": {
"bool":{
"must":[
{
"match":{
"employees.position": "intern"
}
}
],
"filter":[
{
"term":{
"employees.gender.keyword": {
"value":"F"
}
}
}
]
}
}
}
}
}
|
{
"took" : 9,
"timed_out" : false,
"_shards" : {
"total" : 5,
"successful" : 5,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : 1,
"max_score" : 1.1005893,
"hits" : [
{
"_index" : "department",
"_type" : "_doc",
"_id" : "1",
"_score" : 1.1005893,
"_source" : {
"name" : "Devlopment",
"employees" : [
{
"name" : "Eric Green",
"age" : 39,
"gender" : "M",
"position" : "Big Data Specialist"
},
{
"name" : "James Taylor",
"age" : 27,
"gender" : "M",
"position" : "Software Developer"
},
{
"name" : "Gray Jenkins",
"age" : 21,
"gender" : "M",
"position" : "Intern"
},
{
"name" : "Julie Powell",
"age" : 26,
"gender" : "F",
"position" : "Intern"
},
{
"name" : "Benjamin Smith",
"age" : 46,
"gender" : "M",
"position" : "Senior Software Engineer"
}
]
},
"inner_hits" : {
"employees" : {
"hits" : {
"total" : 1,
"max_score" : 1.1005893,
"hits" : [
{
"_index" : "department",
"_type" : "_doc",
"_id" : "1",
"_nested" : {
"field" : "employees",
"offset" : 3
},
"_score" : 1.1005893,
"_source" : {
"name" : "Julie Powell",
"age" : 26,
"gender" : "F",
"position" : "Intern"
}
}
]
}
}
}
}
]
}
}
|
위와 같이 employees 중 1개가 match 되었고 array 중 3번째에 위치해 있고
_souce 에는 match 된 employee 를 보여주며 _socre 기준으로 sort 하여 표시해 준다.
Document releation mapping
이런 방식은 결국 department document 에 employees 정보가 저장되는 구조이므로
employee 의 정보만 업데이트 하기 힘들게 된다.
Elasticsearch 에서는 NoSQL 임에도 불구하고 child/parent 를 설정하여
관계를 mapping 할 수 있는 document relation mapping 이라는 기능을 제공한다.
다만 이런 관계형 데이터는 여러 shard 에 저장할 수 없으며 같은 shard 에 있어야 한다고 한다.
위의 경우라면 아래와 같이 type 이 join 이고 relations 을 설정하는 필드를 하나 제공해 주면 된다.
DELETE /department
PUT /department
{
"mappings":{
"_doc":{
"properties" :{
"join_field":{
"type":"join",
"relations":{
"department":"employee"
}
}
}
}
}
}
|
Adding documents with releation
값은 아래와 같이 입력해주면 된다.
주목할 부분은 routing 입력과 join_field.parent 부분이다.
routing 이란 주어진 document 가 어떤 shard 에 속해있는지
확인하거나 새 document 를 indexing 할때 사용하며
기존 document 를 찾을때도 사용하는데 입력값으로
document 의 id 정보를 넣어주면 속한 shard 정보를 리턴해준다.
이 정보가 필요한 이유는 join 의 경우 해당 object 를 같은 shard 에 저장해야 하기 때문에
이 정보를 제공해야 한다고 한다.
PUT /department/_doc/1
{
"name":"Development",
"join_field":"department"
}
PUT /department/_doc/2
{
"name":"Marketing",
"join_field":"department"
}
PUT /department/_doc/3?routing=1
{
"name":"Bo Anderson",
"age":28,
"gender":"M",
"join_field": {
"name":"employee",
"parent":1
}
}
PUT /department/_doc/4?routing=2
{
"name":"John Doe",
"age":44,
"gender":"M",
"join_field": {
"name":"employee",
"parent":2
}
}
PUT /department/_doc/5?routing=1
{
"name":"jane Park",
"age":23,
"gender":"F",
"join_field": {
"name":"employee",
"parent":1
}
}
PUT /department/_doc/6?routing=2
{
"name":"Christina Parker",
"age":29,
"gender":"F",
"join_field": {
"name":"employee",
"parent":2
}
}
|
Query child document by parent ID
employees 중 특정 document 와 관계를 맺은 employee documents 를 검색시에는 아래와 같이 하면 된다.
GET /department/_search
{
"query":{
"parent_id":{
"type":"employee",
"id":1
}
}
}
|
{
"took" : 6,
"timed_out" : false,
"_shards" : {
"total" : 5,
"successful" : 5,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : 2,
"max_score" : 0.13353139,
"hits" : [
{
"_index" : "department",
"_type" : "_doc",
"_id" : "3",
"_score" : 0.13353139,
"_routing" : "1",
"_source" : {
"name" : "Bo Anderson",
"age" : 28,
"gender" : "M",
"join_field" : {
"name" : "employee",
"parent" : 1
}
}
},
{
"_index" : "department",
"_type" : "_doc",
"_id" : "5",
"_score" : 0.13353139,
"_routing" : "1",
"_source" : {
"name" : "jane Park",
"age" : 23,
"gender" : "F",
"join_field" : {
"name" : "employee",
"parent" : 1
}
}
}
]
}
}
|
Query child document by parent
child document 검색시 parent 의 document 를 모르고
parent 의 특정 정보를 아는 경우 아래와 같이 query 하면 된다.
GET /department/_search
{
"query":{
"has_parent":{
"parent_type":"department",
"query":{
"term": {
"name.keyword": "Development"
}
}
}
}
}
|
{
"took" : 8,
"timed_out" : false,
"_shards" : {
"total" : 5,
"successful" : 5,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : 2,
"max_score" : 1.0,
"hits" : [
{
"_index" : "department",
"_type" : "_doc",
"_id" : "3",
"_score" : 1.0,
"_routing" : "1",
"_source" : {
"name" : "Bo Anderson",
"age" : 28,
"gender" : "M",
"join_field" : {
"name" : "employee",
"parent" : 1
}
}
},
{
"_index" : "department",
"_type" : "_doc",
"_id" : "5",
"_score" : 1.0,
"_routing" : "1",
"_source" : {
"name" : "jane Park",
"age" : 23,
"gender" : "F",
"join_field" : {
"name" : "employee",
"parent" : 1
}
}
}
]
}
}
|
Query parent document by child
위 경우와 반대로 child 조건을 포함하고 있는 parent document 를 구해보자.
GET /department/_search
{
"query":{
"has_child":{
"type":"employee",
"query":{
"range": {
"age": {
"gte": 40,
"lte": 50
}
}
}
}
}
}
|
{
"took" : 8,
"timed_out" : false,
"_shards" : {
"total" : 5,
"successful" : 5,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : 1,
"max_score" : 1.0,
"hits" : [
{
"_index" : "department",
"_type" : "_doc",
"_id" : "2",
"_score" : 1.0,
"_source" : {
"name" : "Marketing",
"join_field" : "department"
}
}
]
}
}
|
추가로 has_child 검색시 검색 결과를 sorting 및 filtering 시 아래 링크와 같이 설정할 수 있다고 한다.
Multi level releations mapping
지금까지 단순히 아래와 같이 하나의 relation 이 있는 경우만 고려하였다.
이번에는 아래와 같은 상황을 한번 생각해 보자.
이를 위한 맵핑은 간단하다. 아래와 같이 하면 된다.
PUT /company
{
"mappings": {
"_doc": {
"properties": {
"join_field": {
"type":"join",
"relations": {
"company":["department","supplier"],
"department": "employee"
}
}
}
}
}
}
|
Multi level releations adding documents
입력도 이전과 별 반 다르지 않지만
약간 주의해서 볼 부분이 routing 부분과 parent 입력 부분이 다른 점이다.
PUT /company/_doc/1
{
"name": "My Company Inc.",
"join_field": "company"
}
PUT /company/_doc/2?routing=1
{
"name": "Development",
"join_field": {
"name":"department",
"parent":1
}
}
PUT /company/_doc/3?routing=1
{
"name": "Bo Andersen",
"join_field": {
"name":"employee",
"parent":2
}
}
|
결과적으로 아래 그림과 같은 상황이 된다.
Multi level releations query documents
이번에는 테스트를 위해 몇몇 데이터를 더 넣었다.
PUT /company/_doc/4
{
"name": "Another Company, Inc.",
"join_field": "company"
}
PUT /company/_doc/5?routing=4
{
"name": "Marketing",
"join_field": {
"name":"department",
"parent":4
}
}
PUT /company/_doc/3?routing=4
{
"name": "John Doe",
"join_field": {
"name":"employee",
"parent":5
}
}
|
이 상태에서 John Doe 가 속해있는 company 를 조회해 보자.
마찬가지로 has_child 옵션을 사용하면 되는데
검색순서가 employee->department->company 순으로 되야 하므로
아래와 같은 query 를 만들수 있으며 안쪽부터 query 가 시작된다고 생각하면 되겠다.
GET /company/_search
{
"query":{
"has_child":{
"type":"department",
"query":{
"has_child": {
"type": "employee",
"query": {
"term": {
"name.keyword": "John Doe"
}
}
}
}
}
}
}
|
{
"took" : 6,
"timed_out" : false,
"_shards" : {
"total" : 5,
"successful" : 5,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : 1,
"max_score" : 1.0,
"hits" : [
{
"_index" : "company",
"_type" : "_doc",
"_id" : "4",
"_score" : 1.0,
"_source" : {
"name" : "Another Company, Inc.",
"join_field" : "company"
}
}
]
}
}
|
Relations inner hits
일치 documents 정보를 상세히 보는 방법은 Nested 일때와 다르지 않다.
보고자 하는 documents 에 inner_hits 를 넣으면 된다.
GET /company/_search
{
"query":{
"has_child":{
"type":"department",
"inner_hits": {},
"query":{
"has_child": {
"type": "employee",
"inner_hits": {},
"query": {
"term": {
"name.keyword": "John Doe"
}
}
}
}
}
}
}
|
{
"took" : 17,
"timed_out" : false,
"_shards" : {
"total" : 5,
"successful" : 5,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : 1,
"max_score" : 1.0,
"hits" : [
{
"_index" : "company",
"_type" : "_doc",
"_id" : "4",
"_score" : 1.0,
"_source" : {
"name" : "Another Company, Inc.",
"join_field" : "company"
},
"inner_hits" : {
"department" : {
"hits" : {
"total" : 1,
"max_score" : 1.0,
"hits" : [
{
"_index" : "company",
"_type" : "_doc",
"_id" : "5",
"_score" : 1.0,
"_routing" : "4",
"_source" : {
"name" : "Marketing",
"join_field" : {
"name" : "department",
"parent" : 4
}
},
"inner_hits" : {
"employee" : {
"hits" : {
"total" : 1,
"max_score" : 0.9808292,
"hits" : [
{
"_index" : "company",
"_type" : "_doc",
"_id" : "3",
"_score" : 0.9808292,
"_routing" : "4",
"_source" : {
"name" : "John Doe",
"join_field" : {
"name" : "employee",
"parent" : 5
}
}
}
]
}
}
}
}
]
}
}
}
}
]
}
}
|
'Monitoring > Elasticsearch' 카테고리의 다른 글
12. Aggregation (0) | 2020.01.17 |
---|---|
11. Query Result Options (0) | 2020.01.17 |
09. Compound Query (0) | 2020.01.17 |
08. Query (0) | 2020.01.17 |
07. Analyzer (1) | 2020.01.17 |