Derived 필드 타입

Derived 필드 타입 (Derived field type)

도입 2.15

Derived field(파생 필드)는 기존 필드에 대해 스크립트를 실행해 새 필드를 동적으로 만들 수 있게 해줘요. 기존 필드는 원본 문서를 포함하는 _source 필드에서 가져오거나, 필드의 doc values에서 가져올 수 있어요. 파생 필드를 인덱스 매핑 또는 검색 요청 안에서 정의하면, 일반 필드를 사용하는 것과 같은 방식으로 쿼리에서 사용할 수 있어요.

출처: 문서

본문

파생 필드를 언제 사용할까요

파생 필드는 필드 조작의 유연성을 제공하고 저장 효율성을 우선시해요. 하지만 쿼리 시점에 계산되기 때문에 쿼리 성능을 떨어뜨릴 수 있어요. 파생 필드는 실시간 데이터 변형이 필요한 시나리오에서 특히 유용한데, 예를 들면 다음과 같아요.

  • 로그 분석: 로그 메시지에서 타임스탬프와 로그 레벨 추출.
  • 성능 지표: 시작 및 종료 타임스탬프에서 응답 시간 계산.
  • 보안 분석: 위협 탐지를 위한 실시간 IP 지리적 위치(geolocation) 및 user-agent 파싱.
  • 실험적 사용 사례: 새 데이터 변형 테스트, A/B 테스트용 임시 필드 생성, 매핑 변경이나 데이터 재색인 없이 일회성 보고서 생성.

쿼리 시점 계산이 미치는 성능 영향에도 불구하고, 파생 필드의 유연성과 저장 효율성은 이러한 애플리케이션에 유용한 도구가 돼요.

현재 제한 사항

현재 파생 필드에는 다음과 같은 제한 사항이 있어요.

  • 점수 및 정렬: 아직 지원되지 않아요.
  • 집계: OpenSearch 2.17부터 파생 필드는 대부분의 집계 타입을 지원해요. 다음 집계는 지원되지 않아요: geographic(geodistance, geohash grid, geohex grid, geotile grid, geobounds, geocentroid), significant terms, significant text, scripted metric.
  • 대시보드 지원: 이 필드는 OpenSearch Dashboards의 사용 가능한 필드 목록에 표시되지 않아요. 하지만 파생 필드 이름을 알면 필터링에 사용할 수는 있어요.
  • 연쇄 파생 필드: 하나의 파생 필드를 사용해 다른 파생 필드를 정의할 수 없어요.
  • Join 필드 타입: 파생 필드는 join 필드 타입에 대해 지원되지 않아요.

이러한 제한 사항은 향후 버전에서 해결할 계획이에요.

사전 요구 사항

파생 필드를 사용하기 전에 다음 사전 요구 사항을 충족하는지 확인하세요.

  • _source 또는 doc_values 활성화: 스크립트에서 사용하는 필드에 대해 _source 필드 또는 doc values가 활성화되어 있는지 확인하세요.
  • 비용이 큰 쿼리 활성화: search.allow_expensive_queries가 true로 설정되어 있는지 확인하세요.
  • 기능 제어: 파생 필드는 기본적으로 활성화돼 있어요. 다음 설정을 사용해 파생 필드를 활성화하거나 비활성화할 수 있어요.
    • 인덱스 레벨: index.query.derived_field.enabled 설정을 업데이트.
    • 클러스터 레벨: search.derived_field.enabled 설정을 업데이트. 두 설정 모두 동적이므로 재색인이나 노드 재시작 없이 변경할 수 있어요.
  • 성능 고려 사항: 파생 필드를 사용하기 전에 성능 영향을 평가해 파생 필드가 스케일 요구 사항을 충족하는지 확인하세요.

파생 필드 정의하기

파생 필드는 인덱스 매핑 또는 검색 요청 안에서 직접 정의할 수 있어요.

예제 설정

이 페이지의 예제를 실행하려면 먼저 다음 logs 인덱스를 만들어요.

PUT logs
{
  "mappings": {
    "properties": {
      "request": {
        "type": "text",
        "fields": {
          "keyword": {
            "type": "keyword"
          }
        }
      },
      "clientip": {
        "type": "keyword"
      }
    }
  }
}

인덱스에 샘플 문서를 추가해요.

POST _bulk
{ "index" : { "_index" : "logs", "_id" : "1" } }
{ "request": "894030400 GET /english/images/france98_venues.gif HTTP/1.0 200 778", "clientip": "61.177.2.0" }
{ "index" : { "_index" : "logs", "_id" : "2" } }
{ "request": "894140400 GET /french/playing/mascot/mascot.html HTTP/1.1 200 5474", "clientip": "185.92.2.0" }
{ "index" : { "_index" : "logs", "_id" : "3" } }
{ "request": "894250400 POST /english/venues/images/venue_header.gif HTTP/1.0 200 711", "clientip": "61.177.2.0" }
{ "index" : { "_index" : "logs", "_id" : "4" } }
{ "request": "894360400 POST /images/home_fr_button.gif HTTP/1.1 200 2140", "clientip": "129.178.2.0" }
{ "index" : { "_index" : "logs", "_id" : "5" } }
{ "request": "894470400 DELETE /images/102384s.gif HTTP/1.0 200 785", "clientip": "227.177.2.0" }

인덱스 매핑에서 파생 필드 정의하기

logs 인덱스에 인덱싱된 request 필드에서 timestamp, method, size 필드를 파생하려면 다음 매핑을 구성해요.

PUT /logs/_mapping
{
  "derived": {
    "timestamp": {
      "type": "date",
      "format": "MM/dd/yyyy",
      "script": {
        "source": """
        emit(Long.parseLong(doc["request.keyword"].value.splitOnToken(" ")[0]))
        """
      }
    },
    "method": {
      "type": "keyword",
      "script": {
        "source": """
        emit(doc["request.keyword"].value.splitOnToken(" ")[1])
        """
      }
    },
    "size": {
      "type": "long",
      "script": {
        "source": """
        emit(Long.parseLong(doc["request.keyword"].value.splitOnToken(" ")[5]))
        """
      }
    }
  }
}

timestamp 필드에는 날짜 필드를 표시할 형식을 지정하는 추가 format 파라미터가 있다는 점을 참고하세요. format 파라미터를 포함하지 않으면 형식은 strict_date_time_no_millis로 기본 설정돼요. 지원되는 날짜 형식에 대한 자세한 내용은 파라미터를 참조하세요.

파라미터

다음 표는 파생 필드 타입이 받아들이는 파라미터를 나열해요. 모든 파라미터는 동적이며 문서를 재색인하지 않고 수정할 수 있어요.

파라미터 필수/선택 설명
type 필수 파생 필드의 타입. 지원되는 타입은 boolean, date, geo_point, ip, keyword, text, long, double, float, object예요.
script 필수 파생 필드와 연결된 스크립트. 스크립트에서 내보낸 값은 반드시 emit()을 사용해 내보내야 해요. 내보낸 값의 타입은 파생 필드의 타입과 일치해야 해요. 스크립트는 활성화된 경우 doc_values와 _source 필드 모두에 접근할 수 있어요. 필드의 doc value는 doc['field_name'].value로 접근할 수 있고, 소스는 params._source["field_name"]으로 접근할 수 있어요.
format 선택 날짜를 파싱하는 데 사용되는 형식. date 필드에만 적용돼요. 유효한 값은 strict_date_time_no_millis, strict_date_optional_time, epoch_millis예요. 자세한 내용은 형식을 참조하세요.
ignore_malformed 선택 파생 필드에 대한 쿼리를 실행할 때 잘못된 형식의 값을 무시할지 여부를 지정하는 Boolean 값. 기본값은 false(잘못된 형식의 값을 만나면 예외를 던짐)예요.
prefilter_field 선택 파생 필드의 성능을 높이기 위해 제공되는 인덱스된 텍스트 필드. 파생 필드에서 필터링하기 전에 필터링할 기존 인덱스 필드를 지정해요. 자세한 내용은 Prefilter field를 참조하세요.

스크립트에서 값 내보내기 (Emitting values)

emit() 함수는 파생 필드 스크립트 컨텍스트 안에서만 사용할 수 있어요. 스크립트가 실행되는 문서에 대해 하나 또는 여러 개(다중 값 필드의 경우)의 스크립트 값을 내보내는 데 사용돼요.

다음 표는 지원되는 필드 타입에 대한 emit() 함수 형식을 나열해요.

타입 Emit 형식 다중 값 필드 지원
boolean emit(boolean) No
double emit(double) Yes
date emit(long timeInMilis) Yes
float emit(float) Yes
geo_point emit(double lat, double lon) Yes
ip emit(String ip) Yes
keyword emit(String) Yes
long emit(long) Yes
object emit(String json) (유효한 JSON) Yes
text emit(String) Yes

기본적으로 파생 필드와 내보낸 값 사이에 타입 불일치가 있으면 검색 요청이 오류와 함께 실패해요. ignore_malformed가 true로 설정되면 실패한 문서는 건너뛰고 검색 요청은 성공해요.

내보낸 값의 크기 제한은 문서당 1MB예요.

인덱스 매핑에서 정의된 파생 필드 검색하기

파생 필드를 검색할 때는 일반 필드를 검색할 때와 동일한 구문을 사용해요. 예를 들어 다음 요청은 지정된 범위 안에 있는 파생 timestamp 필드를 가진 문서를 검색해요.

POST /logs/_search
{
  "query": {
    "range": {
      "timestamp": {
        "gte": "1970-01-11T08:20:30.400Z",   
        "lte": "1970-01-11T08:26:00.400Z"
      }
    }
  },
  "fields": ["timestamp"]
}

응답에는 일치하는 문서가 포함돼요.

{
  "took": 315,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 4,
      "relation": "eq"
    },
    "max_score": 1,
    "hits": [
      {
        "_index": "logs",
        "_id": "1",
        "_score": 1,
        "_source": {
          "request": "894030400 GET /english/images/france98_venues.gif HTTP/1.0 200 778",
          "clientip": "61.177.2.0"
        },
        "fields": {
          "timestamp": [
            "1970-01-11T08:20:30.400Z"
          ]
        }
      },
      {
        "_index": "logs",
        "_id": "2",
        "_score": 1,
        "_source": {
          "request": "894140400 GET /french/playing/mascot/mascot.html HTTP/1.1 200 5474",
          "clientip": "185.92.2.0"
        },
        "fields": {
          "timestamp": [
            "1970-01-11T08:22:20.400Z"
          ]
        }
      },
      {
        "_index": "logs",
        "_id": "3",
        "_score": 1,
        "_source": {
          "request": "894250400 POST /english/venues/images/venue_header.gif HTTP/1.0 200 711",
          "clientip": "61.177.2.0"
        },
        "fields": {
          "timestamp": [
            "1970-01-11T08:24:10.400Z"
          ]
        }
      },
      {
        "_index": "logs",
        "_id": "4",
        "_score": 1,
        "_source": {
          "request": "894360400 POST /images/home_fr_button.gif HTTP/1.1 200 2140",
          "clientip": "129.178.2.0"
        },
        "fields": {
          "timestamp": [
            "1970-01-11T08:26:00.400Z"
          ]
        }
      }
    ]
  }
}

검색 요청에서 파생 필드 정의 및 검색하기

파생 필드를 검색 요청 안에서 직접 정의하고 일반 인덱스 필드와 함께 쿼리할 수도 있어요. 예를 들어 다음 요청은 url과 status 파생 필드를 만들고 일반 request 및 clientip 필드와 함께 해당 필드를 검색해요.

POST /logs/_search
{
  "derived": {
    "url": {
      "type": "text",
      "script": {
        "source": """
        emit(doc["request"].value.splitOnToken(" ")[2])
        """
      }
    },
    "status": {
      "type": "keyword",
      "script": {
        "source": """
        emit(doc["request"].value.splitOnToken(" ")[4])
        """
      }
    }
  },
  "query": {
    "bool": {
      "must": [
        {
          "term": {
            "clientip": "61.177.2.0"
          }
        },
        {
          "match": {
            "url": "images"
          }
        },
        {
          "term": {
            "status": "200"
          }
        }
      ]
    }
  },
  "fields": ["request", "clientip", "url", "status"]
}

응답에는 일치하는 문서가 포함돼요.

{
  "took": 6,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 2,
      "relation": "eq"
    },
    "max_score": 2.8754687,
    "hits": [
      {
        "_index": "logs",
        "_id": "1",
        "_score": 2.8754687,
        "_source": {
          "request": "894030400 GET /english/images/france98_venues.gif HTTP/1.0 200 778",
          "clientip": "61.177.2.0"
        },
        "fields": {
          "request": [
            "894030400 GET /english/images/france98_venues.gif HTTP/1.0 200 778"
          ],
          "clientip": [
            "61.177.2.0"
          ],
          "url": [
            "/english/images/france98_venues.gif"
          ],
          "status": [
            "200"
          ]
        }
      },
      {
        "_index": "logs",
        "_id": "3",
        "_score": 2.8754687,
        "_source": {
          "request": "894250400 POST /english/venues/images/venue_header.gif HTTP/1.0 200 711",
          "clientip": "61.177.2.0"
        },
        "fields": {
          "request": [
            "894250400 POST /english/venues/images/venue_header.gif HTTP/1.0 200 711"
          ],
          "clientip": [
            "61.177.2.0"
          ],
          "url": [
            "/english/venues/images/venue_header.gif"
          ],
          "status": [
            "200"
          ]
        }
      }
    ]
  }
}

파생 필드는 검색 중에 인덱스 분석 설정에 지정된 기본 분석기를 사용해요. 기본 분석기를 재정의하거나 검색 요청 안에서 일반 필드와 같은 방식으로 검색 분석기를 지정할 수 있어요. 자세한 내용은 분석기(Analyzers)를 참조하세요.

필드에 대해 인덱스 매핑과 검색 정의가 모두 존재하면 검색 정의가 우선해요.

필드 검색 (Retrieving fields)

앞선 예제에서 볼 수 있듯이 검색 요청의 fields 파라미터를 사용해 일반 필드와 같은 방식으로 파생 필드를 검색할 수 있어요. 또한 와일드카드를 사용해 주어진 패턴과 일치하는 모든 파생 필드를 검색할 수도 있어요.

하이라이팅

type이 text인 파생 필드는 unified highlighter를 사용한 하이라이팅을 지원해요. 예를 들어 다음 요청은 파생 url 필드를 하이라이팅하도록 지정해요.

POST /logs/_search
{
  "derived": {
    "url": {
      "type": "text",
      "script": {
        "source": """
        emit(doc["request"].value.splitOnToken(" " )[2])
        """
      }
    }
  },
  "query": {
    "bool": {
      "must": [
        {
          "term": {
            "clientip": "61.177.2.0"
          }
        },
        {
          "match": {
            "url": "images"
          }
        }
      ]
    }
  },
  "fields": ["request", "clientip", "url"],
  "highlight": {
    "fields": {
      "url": {}
    }
  }
}

응답은 url 필드에 하이라이팅을 지정해요.

{
  "took": 45,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 2,
      "relation": "eq"
    },
    "max_score": 1.8754687,
    "hits": [
      {
        "_index": "logs",
        "_id": "1",
        "_score": 1.8754687,
        "_source": {
          "request": "894030400 GET /english/images/france98_venues.gif HTTP/1.0 200 778",
          "clientip": "61.177.2.0"
        },
        "fields": {
          "request": [
            "894030400 GET /english/images/france98_venues.gif HTTP/1.0 200 778"
          ],
          "clientip": [
            "61.177.2.0"
          ],
          "url": [
            "/english/images/france98_venues.gif"
          ]
        },
        "highlight": {
          "url": [
            "/english/images/france98_venues.gif"
          ]
        }
      },
      {
        "_index": "logs",
        "_id": "3",
        "_score": 1.8754687,
        "_source": {
          "request": "894250400 POST /english/venues/images/venue_header.gif HTTP/1.0 200 711",
          "clientip": "61.177.2.0"
        },
        "fields": {
          "request": [
            "894250400 POST /english/venues/images/venue_header.gif HTTP/1.0 200 711"
          ],
          "clientip": [
            "61.177.2.0"
          ],
          "url": [
            "/english/venues/images/venue_header.gif"
          ]
        },
        "highlight": {
          "url": [
            "/english/venues/images/venue_header.gif"
          ]
        }
      }
    ]
  }
}

집계 (Aggregations)

OpenSearch 2.17부터 파생 필드는 대부분의 집계 타입을 지원해요.

geographic, significant terms, significant text, scripted metric 집계는 지원되지 않아요.

예를 들어 다음 요청은 method 파생 필드에 간단한 terms 집계를 만들어요.

POST /logs/_search
{
  "size": 0,
  "aggs": {
    "methods": {
      "terms": {
        "field": "method"
      }
    }
  }
}

응답에는 다음 버킷이 포함돼요.

{
  "took" : 12,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 5,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [ ]
  },
  "aggregations" : {
    "methods" : {
      "doc_count_error_upper_bound" : 0,
      "sum_other_doc_count" : 0,
      "buckets" : [
        {
          "key" : "GET",
          "doc_count" : 2
        },
        {
          "key" : "POST",
          "doc_count" : 2
        },
        {
          "key" : "DELETE",
          "doc_count" : 1
        }
      ]
    }
  }
}

성능 (Performance)

파생 필드는 인덱싱되지 않고 _source 필드 또는 doc values에서 값을 검색해 동적으로 계산돼요. 따라서 더 느리게 실행돼요. 성능을 개선하려면 다음을 시도해 보세요.

  • 파생 필드와 함께 인덱스된 필드에 대한 쿼리 필터를 추가해 검색 공간을 줄이세요.
  • 가능할 때마다 스크립트에서 _source 대신 doc values를 사용해 더 빠르게 접근하세요.
  • 검색 요청에 명시적인 필터를 추가하지 않고도 검색 공간을 자동으로 줄이도록 prefilter_field를 사용하는 것을 고려하세요.

Prefilter field

prefilter field를 지정하면 검색 요청에 명시적인 필터를 추가하지 않아도 검색 공간을 줄이는 데 도움이 돼요. prefilter field는 쿼리를 구성할 때 자동으로 필터링 대상인 기존 인덱스 필드(prefilter_field)를 지정해요. prefilter_field는 text 필드(text 또는 match_only_text)여야 해요.

예를 들어 method 파생 필드에 prefilter_field를 추가할 수 있어요. request 필드에서 사전 필터링하도록 지정하며 인덱스 매핑을 업데이트해요.

PUT /logs/_mapping
{
  "derived": {
    "method": {
      "type": "keyword",
      "script": {
        "source": """
        emit(doc["request.keyword"].value.splitOnToken(" ")[1])
        """
      },
      "prefilter_field": "request"
    }
  }
}

이제 method 파생 필드에 대한 쿼리를 사용해 검색해요.

POST /logs/_search
{
  "profile": true,
  "query": {
    "term": {
      "method": {
        "value": "GET"
      }
    }
  },
  "fields": ["method"]
}

OpenSearch는 request 필드에 대한 필터를 쿼리에 자동으로 추가해요.

"#request:GET #DerivedFieldQuery (Query: [ method:GET])"

앞선 예제에서 볼 수 있듯이 profile 옵션을 사용해 파생 필드 성능을 분석할 수 있어요.

파생 object 필드 (Derived object fields)

스크립트는 유효한 JSON 객체를 내보낼 수 있어서 일반 필드와 같은 방식으로 하위 필드를 인덱싱하지 않고도 쿼리할 수 있어요. 이는 일부 하위 필드에 대해 가끔 검색이 필요한 큰 JSON 객체에 유용해요. 이 경우 하위 필드를 인덱싱하는 것은 비용이 많이 들고, 각 하위 필드에 대해 파생 필드를 정의하는 것도 많은 리소스 오버헤드를 추가해요. 하위 필드 타입을 명시적으로 제공하지 않으면 하위 필드 타입은 추론돼요.

예를 들어 다음 요청은 derived_request_object 파생 필드를 object 타입으로 정의해요.

PUT logs_object
{
  "mappings": {
    "properties": {
      "request_object": { "type": "text" }
    },
    "derived": {
      "derived_request_object": {
        "type": "object",
        "script": {
          "source": "emit(params._source[\"request_object\"])"
        }
      }
    }
  }
}

request_object가 JSON 객체의 문자열 표현인 다음 문서를 생각해 보세요.

POST _bulk
{ "index" : { "_index" : "logs_object", "_id" : "1" } }
{ "request_object": "{\"@timestamp\": 894030400, \"clientip\":\"61.177.2.0\", \"request\": \"GET /english/venues/images/venue_header.gif HTTP/1.0\", \"status\": 200, \"size\": 711}" }
{ "index" : { "_index" : "logs_object", "_id" : "2" } }
{ "request_object": "{\"@timestamp\": 894140400, \"clientip\":\"129.178.2.0\", \"request\": \"GET /images/home_fr_button.gif HTTP/1.1\", \"status\": 200, \"size\": 2140}" }
{ "index" : { "_index" : "logs_object", "_id" : "3" } }
{ "request_object": "{\"@timestamp\": 894240400, \"clientip\":\"227.177.2.0\", \"request\": \"GET /images/102384s.gif HTTP/1.0\", \"status\": 400, \"size\": 785}" }
{ "index" : { "_index" : "logs_object", "_id" : "4" } }
{ "request_object": "{\"@timestamp\": 894340400, \"clientip\":\"61.177.2.0\", \"request\": \"GET /english/images/venue_bu_city_on.gif HTTP/1.0\", \"status\": 400, \"size\": 1397}\n" }
{ "index" : { "_index" : "logs_object", "_id" : "5" } }
{ "request_object": "{\"@timestamp\": 894440400, \"clientip\":\"132.176.2.0\", \"request\": \"GET /french/news/11354.htm HTTP/1.0\", \"status\": 200, \"size\": 3460, \"is_active\": true}" }

다음 쿼리는 derived_request_object의 @timestamp 하위 필드를 검색해요.

POST /logs_object/_search
{
  "query": {
    "range": {
      "derived_request_object.@timestamp": {
        "gte": "894030400",   
        "lte": "894140400"
      }
    }
  },
  "fields": ["derived_request_object.@timestamp"]
}

응답에는 일치하는 문서가 포함돼요.

{
  "took": 26,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 2,
      "relation": "eq"
    },
    "max_score": 1,
    "hits": [
      {
        "_index": "logs_object",
        "_id": "1",
        "_score": 1,
        "_source": {
          "request_object": """{"@timestamp": 894030400, "clientip":"61.177.2.0", "request": "GET /english/venues/images/venue_header.gif HTTP/1.0", "status": 200, "size": 711}"""
        },
        "fields": {
          "derived_request_object.@timestamp": [
            894030400
          ]
        }
      },
      {
        "_index": "logs_object",
        "_id": "2",
        "_score": 1,
        "_source": {
          "request_object": """{"@timestamp": 894140400, "clientip":"129.178.2.0", "request": "GET /images/home_fr_button.gif HTTP/1.1", "status": 200, "size": 2140}"""
        },
        "fields": {
          "derived_request_object.@timestamp": [
            894140400
          ]
        }
      }
    ]
  }
}

파생 object 필드를 하이라이팅하도록 지정할 수도 있어요.

POST /logs_object/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "term": {
            "derived_request_object.clientip": "61.177.2.0"
          }
        },
        {
          "match": {
            "derived_request_object.request": "images"
          }
        }
      ]
    }
  },
  "fields": ["derived_request_object.*"],
  "highlight": {
    "fields": {
      "derived_request_object.request": {}
    }
  }
}

응답은 derived_request_object.request 필드에 하이라이팅을 추가해요.

{
  "took": 5,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 2,
      "relation": "eq"
    },
    "max_score": 2,
    "hits": [
      {
        "_index": "logs_object",
        "_id": "1",
        "_score": 2,
        "_source": {
          "request_object": """{"@timestamp": 894030400, "clientip":"61.177.2.0", "request": "GET /english/venues/images/venue_header.gif HTTP/1.0", "status": 200, "size": 711}"""
        },
        "fields": {
          "derived_request_object.request": [
            "GET /english/venues/images/venue_header.gif HTTP/1.0"
          ],
          "derived_request_object.clientip": [
            "61.177.2.0"
          ]
        },
        "highlight": {
          "derived_request_object.request": [
            "GET /english/venues/images/venue_header.gif HTTP/1.0"
          ]
        }
      },
      {
        "_index": "logs_object",
        "_id": "4",
        "_score": 2,
        "_source": {
          "request_object": """{"@timestamp": 894340400, "clientip":"61.177.2.0", "request": "GET /english/images/venue_bu_city_on.gif HTTP/1.0", "status": 400, "size": 1397}
"""
        },
        "fields": {
          "derived_request_object.request": [
            "GET /english/images/venue_bu_city_on.gif HTTP/1.0"
          ],
          "derived_request_object.clientip": [
            "61.177.2.0"
          ]
        },
        "highlight": {
          "derived_request_object.request": [
            "GET /english/images/venue_bu_city_on.gif HTTP/1.0"
          ]
        }
      }
    ]
  }
}
추론된 하위 필드 타입 (Inferred subfield type)

타입 추론은 동적 매핑(Dynamic mapping)과 동일한 논리에 기반해요. 첫 번째 문서에서 하위 필드 타입을 추론하는 대신, 문서의 무작위 샘플을 사용해 타입을 추론해요. 하위 필드가 무작위 샘플의 어떤 문서에서도 발견되지 않으면 타입 추론은 실패하고 경고를 기록해요. 문서에서 드물게 발생하는 하위 필드의 경우 명시적 필드 타입을 정의하는 것을 고려하세요. 이러한 하위 필드에 동적 타입 추론을 사용하면 누락된 필드처럼 쿼리가 결과를 반환하지 않을 수 있어요.

명시적 하위 필드 타입 (Explicit subfield type)

명시적 하위 필드 타입을 정의하려면 properties 객체에 type 파라미터를 제공해요. 다음 예제에서 derived_logs_object.is_active 필드는 boolean으로 정의돼요. 이 필드는 문서 중 하나에만 존재하므로 타입 추론이 실패할 수 있어서 명시적 타입을 정의하는 것이 중요해요.

POST /logs_object/_search
{
  "derived": {
    "derived_request_object": {
      "type": "object",
      "script": {
        "source": "emit(params._source[\"request_object\"])"
      },
      "properties": {
        "is_active": "boolean"
      }
    }
  },
  "query": {
    "term": {
      "derived_request_object.is_active": true
    }
  },
  "fields": ["derived_request_object.is_active"]
}

응답에는 일치하는 문서가 포함돼요.

{
  "took": 13,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 1,
      "relation": "eq"
    },
    "max_score": 1,
    "hits": [
      {
        "_index": "logs_object",
        "_id": "5",
        "_score": 1,
        "_source": {
          "request_object": """{"@timestamp": 894440400, "clientip":"132.176.2.0", "request": "GET /french/news/11354.htm HTTP/1.0", "status": 200, "size": 3460, "is_active": true}"""
        },
        "fields": {
          "derived_request_object.is_active": [
            true
          ]
        }
      }
    ]
  }
}

출처: 문서

더 알아보기 (Learn more)