Study/elasticsearch

Elasticsearch object와 nested 타입 비교

voider 2023. 8. 5. 15:50

object와 nested 모두 필드 하위에 다른 필드가 들어가는 계층 구조의 데이터를 담는 타입이다. 이 둘은 유사하지만 배열을 처리할 때 동작하는 방식이 다르다.

object vs. nested

  • object
    • 용도: 일반적인 계층 구조에 사용
    • 성능: 상대적으로 가벼움
    • 검색: 일반적인 쿼리를 사용한다.
  • nested
    • 용도: 배열 내 각 객체를 독립적으로 취급해야 하는 특수한 상황에서 사용
    • 성능: 상대적으로 무거움. 내부적으로 숨겨진 문서를 생성
    • 검색: 전용 nested 쿼리로 감싸서 사용해야 한다.

object type

JSON 문서는 필드의 하위에 다른 필드를 여럿 포함하는 객체 데이터를 담는다. object가 객체 데이터의 기본 타입이다. object와 nested의 차이는 객체 배열에 대해 쿼리할 때 나타난다.

아래 테스트 데이터를 색인한다.

PUT object_test/_doc/1
{
  "price": 2770.75,
  "spec": {
    "cores": 12,
    "memory": "128",
    "storage": 8000
  }
}

PUT object_test/_doc/2
{
  "spec": [
    {
      "cores": 12,
      "memory": 128,
      "storage": 8000
    },
    {
      "cores": 6,
      "memory": 64,
      "storage": 8000
    },
    {
      "cores": 6,
      "memory": 32,
      "storage": 4000
    }
  ]
}

이 인덱스에서 spec.cores가 6개이며, spec.memory는 128인 문서를 검색하면 아래와 같다.

GET object_test/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "term": {
            "spec.cores": "6"
          }
        },
        {
          "term": {
            "spec.memory": "128"
          }
        }
      ]
    }
  }

# 결과
  {
	"_index": "object_test",
	"_id": "2",
	"_score": 1.1513612,
	"_source": {
	  "spec": [
		{
		  "cores": 12,
		  "memory": 128,
		  "storage": 8000
		},
		{
		  "cores": 6,
		  "memory": 64,
		  "storage": 8000
		},
		{
		  "cores": 6,
		  "memory": 32,
		  "storage": 4000
		}
	  ]
	}
  }

결과를 보면 cores가 6이면서, memory가 128인 문서는 존재하지 않는데 검색이 됐다. 이 검색 결과로 object 타입 데이터가 어떻게 평탄화되는지 알 수 있다. 위 문서는 내부적으로 다음과 같이 평탄화 된다.

{
	"spec.cores": [12, 6, 6],
	"spec.memory": [128, 64, 32],
	"spec.storage": [8000, 8000, 4000]
}

spec.cores의 역색인에서 6을 찾을 수 있고, spec.memory에서 128을 찾을 수 있으니 해당 object가 검색 결과로 나타난다. 이처럼 object 타입 배열은 배열을 구성하는 객체 데이터를 서로 독립적인 데이터로 취급하지 않는다. 어떤 경우에도 독립적이어야 한다면 nested 타입을 사용해야 한다.

nested type

nested 타입은 object 타입과 다르게 배열 내 각 객체를 독립적으로 취급한다. 다음과 같이 type에 nested를 명시해서 새 인덱스를 생성한 후 동일한 내용의 문서를 색인한다.

// create index
PUT nested_test
{
  "mappings": {
    "properties": {
      "spec": {
        "type": "nested",
        "properties": {
          "cores": {
            "type": "long"
          },
          "memory": {
            "type": "long"
          },
          "storage": {
            "type": "long"
          }
        }
      }
    }
  }
}

// index document
PUT nested_test/_doc/1
{
  "spec": [
    {
      "cores": 12,
      "memory": 128,
      "storage": 8000
    },
    {
      "cores": 6,
      "memory": 64,
      "storage": 8000
    },
    {
      "cores": 6,
      "memory": 32,
      "storage": 4000
    }
  ]
}

// search
GET nested_test/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "term": {
            "spec.cores": {
              "value": "6"
            }
          }
        },
        {
          "term": {
            "spec.memory": {
              "value": "128"
            }
          }
        }
      ]
    }
  }
}

위와 같은 순서로 진행하면 object 타입과 달리 일치하는 문서가 없다는 결과를 얻는다. 여기서 이상한 점은 쿼리를 조금 수정해서 존재하는 문서를 조회해도 결과가 없다는 것이다.

GET nested_test/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "term": {
            "spec.cores": {
              "value": "12"
            }
          }
        },
        {
          "term": {
            "spec.memory": {
              "value": "128"
            }
          }
        }
      ]
    }
  }
}

코어 개수가 12개이면서, 메모리가 128인 문서는 실제로 존재하는데도 결과는 null이다. nested 타입은 객체 배열의 각 객체를 내부적으로 별도의 루씬 문서로 분리해 저장한다. 배열 원소가 100개라면 부모 문서까지 101개의 문서가 내부적으로 생성된다. nested의 이런 동작 방식은 엘라스틱서치 내에서 굉장히 특수하다. 때문에 nested 쿼리라는 전용 쿼리를 이용해서 검색해야 한다.

nested 쿼리를 지정하고, path 부분에 검색 대상이 될 nested 타입 필드 이름을 지정한다. 그 다음 query 절을 작성한다.

nested query

GET nested_test/_search
{
  "query": {
    "nested": {
      "path": "spec",
      "query": {
        "bool": {
          "must": [
            {
              "term": {
                "spec.cores": {
                  "value": "12"
                }
              }
            },
            {
              "term": {
                "spec.memory": {
                  "value": "128"
                }
              }
            }
          ]
        }
      }
    }
  }
}

nested 타입은 내부적으로 각 객체를 별도의 문서로 분리해서 저장한다. 따라서 성능 문제가 있을 수 있다.