엔지니어링

Elasticsearch의 시계열 데이터 쿼리 및 집계 작업

일반적으로 Elasticsearch는 검색 엔진이며, Lucene 데이터베이스에 그 검색 인덱스를 유지합니다. 그러나 처음 시작된 이래 Elasticsearch는 그 자체가 고성능의 클러스터와 확장이 가능한 데이터 저장소로 진화해왔습니다. 그 색인 형식은 여전히 처음 시작되었던 때를 반영하지만, 모든 종류의 사용자가 모든 종류의 목적으로 이를 사용합니다.

이러한 목적 중 하나는 시계열 데이터를 저장, 처리 및 검색하는 것입니다. 시계열 데이터는 정확한 타임스탬프와 연관되는 모든 데이터 요소를 특징으로 합니다. 가장 빈번하게는, 데이터 요소가 특정 시점에서 이루어진 일종의 측정을 나타냅니다. 이것은 주가일 수도 있고, 과학적인 관찰일 수도 있고, 서버의 로드일 수도 있습니다.

시계열 데이터의 취급에 특화된 몇 가지 데이터베이스 구현이 존재하지만, 시계열이 저장되고 쿼리되는 일반적인 형식은 없습니다. 이론적으로는 어떤 데이터베이스 엔진도 시계열 데이터를 취급하는 데 사용될 수 있습니다.

추상 형식으로, 시계열 데이터베이스 항목은 다음으로 이루어집니다.

  • 계열 이름
  • 타임스탬프
  • 계열에 대한 추가 정보를 포함하는 키-값 쌍 또는 태그

서버 모니터링 사용 사례에는, 언제나 시계열이 어느 호스트에 속하는지를 지정하는 하나의 키-값 쌍이 존재하게 됩니다. 그러나 더해질 수 있는 추가 정보는 어떤 것이든 될 수 있으며, 특정한 호스트 세트에 대한 메트릭을 요청하기 위해서만 나중에 사용될 수도 있습니다. 예제는 프로덕션 환경에 속하는 서비스나 특정 클라우드 서비스 제공자에서만 실행되는 인스턴스 등 구체적인 서비스를 실행하는 호스트가 될 것입니다.

이것을 더 실용적으로 만들기 위해, Elasticsearch 쿼리를 사용하여 데이터에서 특정 시계열 정보를 걸러낼 수 있는 방법의 예제로 Metricbeat 데이터를 사용해 보겠습니다.

각 Metricbeat 문서에는 다음 정보가 포함됩니다.

  • 타임스탬프
  • 실제 시계열 데이터
    • Metricbeat의 경우, 문서의 이 부분의 형식에 대해서는 Metricbeat 설명서에 설명되어 있습니다. 시스템 모듈의 cpu 메트릭 세트에 대해 더 자세히 알아보려면, 메트릭 세트 설명서를 참조하세요.
  • 문서에 포함되어 있는 메트릭 그 자체에 대한 메타데이터 Metricbeat는 ECS 필드 event.moduleevent.dataset를 사용하여 어느 Metricbeat 모듈이 문서를 생성했는지, 그리고 어느 메트릭 세트가 문서에 포함되어 있는지를 지정합니다.
    • 이것은 시계열 데이터를 검색하려고 하기에 앞서, 어느 메트릭이 처음에 나타나는지를 찾아내는 데 도움이 될 수 있습니다.
  • 물리적인 호스트이든, 가상 머신이든, 아니면 Kubernetes pod나 Docker 컨테이너 같은 규모가 더 작은 항목이든, 인스턴스에 대한 메타데이터
    • 이 메타데이터는 Elastic Common Schema를 따르며 따라서 역시 ECS를 사용하는 다른 소스의 데이터와 일치될 수 있습니다.

예제로, 이것은 system.cpu 메트릭 세트의 Metricbeat 문서가 어떻게 생겼는지를 보여줍니다. _source 객체에 대한 인라인 주석은 어디에서 필드에 대한 더 자세한 정보를 찾을 수 있는지를 나타냅니다.

유의사항: 문서에는 가독성을 위해 JSON에 # 주석이 포함되어 있습니다.

{ 
  "_index" : "metricbeat-8.0.0-2019.08.12-000001", 
  "_type" : "_doc", 
  "_id" : "vWm5hWwB6vlM0zxdF3Q5", 
  "_score" : 0.0, 
  "_source" : { 
    "@timestamp" : "2019-08-12T12:06:34.572Z", 
    "ecs" : { # ECS metadata 
      "version" : "1.0.1" 
    }, 
    "host" : { # ECS metadata 
      "name" : "noether", 
      "hostname" : "noether", 
      "architecture" : "x86_64", 
      "os" : { 
        "kernel" : "4.15.0-55-generic", 
        "codename" : "bionic", 
        "platform" : "ubuntu", 
        "version" : "18.04.3 LTS (Bionic Beaver)", 
        "family" : "debian", 
        "name" : "Ubuntu" 
      }, 
      "id" : "4e3eb308e7f24789b4ee0b6b873e5414", 
      "containerized" : false 
    }, 
    "agent" : { # ECS metadata 
      "ephemeral_id" : "7c725f8a-ac03-4f2d-a40c-3695a3591699", 
      "hostname" : "noether", 
      "id" : "e8839acc-7f5e-40be-a3ab-1cc891bcb3ce", 
      "version" : "8.0.0", 
      "type" : "metricbeat" 
    }, 
    "event" : { # ECS metadata 
      "dataset" : "system.cpu", 
      "module" : "system", 
      "duration" : 725494 
    }, 
    "metricset" : { # metricbeat metadata 
      "name" : "cpu" 
    }, 
    "service" : { # metricbeat metadata 
      "type" : "system" 
    }, 
    "system" : { # metricbeat time series data  
      "cpu" : { 
        "softirq" : { 
          "pct" : 0.0112 
        }, 
        "steal" : { 
          "pct" : 0 
        }, 
        "cores" : 8, 
        "irq" : { 
          "pct" : 0 
        }, 
        "idle" : { 
          "pct" : 6.9141 
        }, 
        "nice" : { 
          "pct" : 0 
        }, 
        "user" : { 
          "pct" : 0.7672 
        }, 
        "system" : { 
          "pct" : 0.3024 
        }, 
        "iowait" : { 
          "pct" : 0.0051 
        }, 
        "total" : { 
          "pct" : 1.0808 
        } 
      } 
    } 
  } 
}

요약하자면, Metricbeat 문서 내에는 시계열 데이터와 메타데이터가 섞여 있으며, 정확히 필요한 것을 검색하려면 문서 형식에 대한 구체적인 지식이 필요합니다.

대조적으로, 시계열 데이터를 처리, 분석 또는 시각화하고자 하는 경우, 데이터는 통상적으로 이처럼 표와 같은 형식이어야 합니다.

<series name> <timestamp> <value> <key-value pairs> 
system.cpu.user.pct 1565610800000 0.843 host.name=”noether” 
system.cpu.user.pct 1565610800000 0.951 host.name=”hilbert” 
system.cpu.user.pct 1565610810000 0.865 host.name=”noether” 
system.cpu.user.pct 1565610810000 0.793 host.name=”hilbert” 
system.cpu.user.pct 1565610820000 0.802 host.name=”noether” 
system.cpu.user.pct 1565610820000 0.679 host.name=”hilbert”

Elasticsearch 쿼리는 그러한 표에 대단히 근접한 형식의 시계열 데이터를 프로그래밍 방식으로 검색하는 데 도움이 될 수 있으며, 다음 예제는 이렇게 하는 방법을 보여줍니다. 쿼리를 가지고 직접 실험해보고 싶으시면, Elasticsearch 인스턴스와 system.cpusystem.network 메트릭 세트를 위해 데이터를 수집, 전송하는 실행 중인 Metricbeat 설치가 필요할 것입니다. Metricbeat에 대한 간단한 소개는 시작하기 설명서를 참조하세요.

Kibana의 Dev Tools 콘솔에서 모든 쿼리를 실행할 수 있습니다. 이전에 사용해보신 적이 없는 경우, Kibana 콘솔 설명서에서 간단한 소개를 찾아보실 수 있습니다. 예제 쿼리에서 호스트 이름을 변경해야 한다는 점에 유의하세요.

우리는 Metricbeat가 다음 기본 구성에 따라 설정된 것으로 가정합니다. 이것은 하루에 하나의 인덱스를 만들며, 이러한 인덱스에는 ‘metricbeat-VERSION-DATE-COUNTER’라는 이름이 붙게 된다는 뜻입니다. 즉, metricbeat-7.3.0-2019.08.06-000009 같은 이름이 될 것입니다. 이러한 인덱스를 한 번에 쿼리하기 위해, 다음과 같이 와일드카드를 사용하겠습니다.

예제 쿼리:

GET metricbeat-*/_search

그리고 다음 예제 응답:

{ 
  "took" : 2, 
  "timed_out" : false, 
  "_shards" : { 
    "total" : 1, 
    "successful" : 1, 
    "skipped" : 0, 
    "failed" : 0 
  }, 
  "hits" : { 
    "total" : { 
      "value" : 10000, 
      "relation" : "gte" 
    }, 
    "max_score" : 1.0, 
    "hits" : [...] 
  } 
}

당연히, 이 쿼리는 하나의 쿼리에서 반환되는 Elasticsearch 문서 한도를 초과합니다. 실제 실행 횟수는 여기서 생략되었지만 결과를 스크롤하면서 주석이 달린 위의 문서와 비교해보고 싶으실 수도 있습니다.

모니터링되는 인프라의 크기에 따라, 엄청난 수의 Metricbeat가 있을 수 있지만, (기록된) 시간의 처음부터 시계열이 필요한 경우는 드뭅니다. 따라서 날짜 범위부터 시작해 보겠습니다. 이 경우에는 범위가 지난 5분간입니다.

예제 쿼리:

GET metricbeat-*/_search 
{ 
  "query": { 
    "range": { 
      "@timestamp": { 
        "gte": "now-5m" 
      } 
    } 
  } 
}

그리고 다음 예제 응답:

{ 
  "took" : 4, 
  "timed_out" : false, 
  "_shards" : { 
    "total" : 1, 
    "successful" : 1, 
    "skipped" : 0, 
    "failed" : 0 
  }, 
  "hits" : { 
    "total" : { 
      "value" : 30, 
      "relation" : "eq" 
    }, 
    "max_score" : 0.0, 
    "hits" : [...] 
  } 
}

이제 이것은 훨씬 더 관리하기 쉬운 크기입니다. 그러나, 이 쿼리가 실행되는 시스템은 보고하는 호스트가 단 하나 밖에 없으며, 따라서 프로덕션 환경에서는 실행 횟수의 수치가 여전히 대단히 높게 됩니다.

특정한 호스트에 대한 모든 CPU 데이터를 검색하려면, 처음에 미숙하게 시도하는 Elasticsearch 쿼리는 host.name과 metricset system.cpu를 위한 필터를 추가하는 것이 될 수 있습니다.

예제 쿼리:

GET metricbeat-*/_search 
{ 
  "query": { 
    "bool": { 
      "filter": [ 
        { 
          "range": { 
            "@timestamp": { 
              "gte": "now-5m" 
            } 
          } 
        }, 
        { 
          "bool": { 
            "should": [ 
              { 
                "match_phrase": { 
                  "host.name": "noether" 
                } 
              }, 
              { 
                "match_phrase": { 
                  "event.dataset": "system.cpu" 
                } 
              } 
            ] 
          } 
        } 
      ] 
    } 
  } 
}

그리고 다음 예제 응답:

{ 
  "took" : 8, 
  "timed_out" : false, 
  "_shards" : { 
    "total" : 1, 
    "successful" : 1, 
    "skipped" : 0, 
    "failed" : 0 
  }, 
  "hits" : { 
    "total" : { 
      "value" : 30, 
      "relation" : "eq" 
    }, 
    "max_score" : 0.0, 
    "hits" : [...] 
  } 
}

이 쿼리는 여전히 대량의 문서를 반환하며, 모든 문서가 system.cpu 메트릭 세트에 대해 Metricbeat가 보낸 전체 데이터를 포함하고 있습니다. 몇 가지 이유로, 이것은 대단히 유용한 결과는 아닙니다.

첫째로, 우리는 전체 기간에 걸쳐 모든 문서를 검색해야 할 것입니다. 우리가 구성된 한도에 도달하자마자, Elasticsearch는 한 번에 이것을 반환하지 않지 않고 그 순위를 매기려 할 것이며(이것은 우리 쿼리에는 적합하지 않습니다) 타임스탬프에 따라 정렬된 문서를 반환하지 않을 것입니다.

둘째로, 우리는 타임스탬프, 메트릭 값 몇 개, 그리고 어쩌면 일부 추가적인 메타데이터 필드 등 각 문서의 작은 부분에만 관심이 있습니다. Elasticsearch로부터 전체 _source를 반환한 다음, 쿼리 결과로부터 그 데이터를 골라내는 것은 대단히 비효율적입니다.

이것을 해결하기 위한 한 가지 방법은 Elasticsearch 집계를 사용하는 것입니다.

예제 1: CPU 사용률, 다운샘플링

우선, 날짜 히스토그램을 살펴보겠습니다. 날짜 히스토그램 집계는 시간 간격당 한 개의 값을 반환하게 됩니다. 반환된 버킷은 이미 시간을 기준으로 정렬되어 있으며, 데이터와 일치하는 간격 또는 버킷 크기가 지정될 수 있습니다. 이 예제에서는, Metricbeat가 기본값으로 10초마다 시스템 모듈로부터 데이터를 전송하기 때문에 간격 크기가 10초로 선택되어 있습니다. 최상위 size: 0 매개변수는 우리가 더 이상 실제 적중 횟수에 관심이 없고 집계에만 관심이 있음을 지정하고 있으며, 따라서 어떤 문서도 반환되지 않을 것입니다.

예제 쿼리:

GET metricbeat-*/_search 
{ 
  "query": {...},  # same as above 
  "size": 0, 
  "aggregations": { 
    "myDateHistogram": { 
      "date_histogram": { 
        "field": "@timestamp", 
        "fixed_interval": "10s" 
      } 
    } 
  } 
}

그리고 다음 예제 응답:

{ 
  ..., 
  "hits" : { 
    "total" : { 
      "value" : 30, 
      "relation" : "eq" 
    }, 
    "max_score" : null, 
    "hits" : [ ] 
  }, 
  "aggregations" : { 
    "myDateHistogram" : { 
      "buckets" : [ 
        { 
          "key_as_string" : "2019-08-12T13:03:20.000Z", 
          "key" : 1565615000000, 
          "doc_count" : 1 
        }, 
        { 
          "key_as_string" : "2019-08-12T13:03:30.000Z", 
          "key" : 1565615010000, 
          "doc_count" : 1 
        }, 
        { 
          "key_as_string" : "2019-08-12T13:03:40.000Z", 
          "key" : 1565615020000, 
          "doc_count" : 1 
        }, 
        ... 
      ] 
    } 
  } 
}

각 버킷마다, 이것은 에서 타임스탬프(사람이 읽을 수 있는 날짜/시간 문자열을 포함하는 유용한 key_as_string)와 최종적으로 버킷에 들어있는 문서의 수를 반환합니다.

doc_count는 1입니다. 버킷 크기가 Metricbeat의 보고 주기와 일치하기 때문입니다. 그렇지 않다면 이것은 대단히 유용하지 않습니다. 실제 메트릭 값을 보기 위해, 또 다른 집계를 추가하겠습니다. 이 단계에서 우리는 이 집계가 무엇이 될 것인지 결정해야 합니다. 숫자 값, avg, min, max는 좋은 후보입니다. 그러나 버킷당 문서 하나만 갖는 한, 무엇을 선택하든 사실상 중요하지 않습니다. 다음 예제는 10초 길이의 버킷 내에서 메트릭 system.cpu.user.pct 값에 대한 avg, min, max 집계를 반환하며 이것을 보여줍니다.

예제 쿼리:

GET metricbeat-*/_search 
{ 
  "query": {...},  # same as above 
  "size": 0, 
  "aggregations": { 
    "myDateHistogram": { 
      "date_histogram": { 
        "field": "@timestamp", 
        "fixed_interval": "10s" 
      }, 
      "aggregations": { 
        "myActualCpuUserMax": { 
          "max": { 
            "field": "system.cpu.user.pct" 
          } 
        }, 
        "myActualCpuUserAvg": { 
          "avg": { 
            "field": "system.cpu.user.pct" 
          } 
        }, 
        "myActualCpuUserMin": { 
          "min": { 
            "field": "system.cpu.user.pct" 
          } 
        } 
      } 
    } 
  } 
}

그리고 다음 예제 응답:

{ 
  ..., 
  "hits" : {...}, 
  "aggregations" : { 
    "myDateHistogram" : { 
      "buckets" : [ 
        { 
          "key_as_string" : "2019-08-12T13:12:40.000Z", 
          "key" : 1565615560000, 
          "doc_count" : 1, 
          "myActualCpuUserMin" : { 
            "value" : 1.002 
          }, 
          "myActualCpuUserAvg" : { 
            "value" : 1.002 
          }, 
          "myActualCpuUserMax" : { 
            "value" : 1.002 
          } 
        }, 
        { 
          "key_as_string" : "2019-08-12T13:12:50.000Z", 
          "key" : 1565615570000, 
          "doc_count" : 1, 
          "myActualCpuUserMin" : { 
            "value" : 0.866 
          }, 
          "myActualCpuUserAvg" : { 
            "value" : 0.866 
          }, 
          "myActualCpuUserMax" : { 
            "value" : 0.866 
          } 
        }, 
        ... 
      ] 
    } 
  } 
}

보시는 것처럼, myActualCpuUserMin, myActualCpuUserAvg, myActualCpuUserMax에 대한 값은 각 버킷에서 동일합니다. 따라서 주기적인 간격으로 보고된 시계열의 원시 값을 검색해야 하는 경우, 날짜 히스토그램을 사용해 이를 수행할 수 있습니다.

그러나 대부분의 경우, 모든 데이터 요소 하나 하나에 관심을 갖게 되지 않을 것입니다. 특히 몇 초마다 측정이 이루어질 때는 더 그렇습니다. 많은 목적에 대해, 단위가 더 큰 데이터를 갖는 것이 사실상 더 낫습니다. 예를 들어, 시각화는 시계열에서 변형을 표시하기 위해 제한된 양의 픽셀만을 갖습니다. 따라서 단위가 더 세밀한 데이터는 렌더링 시간 동안 버려지게 됩니다.

시계열 데이터는 그 다음에 이루어지는 처리 단계가 무엇이든 그 요구사항에 맞는 세분성 단위로 다운샘플링되는 경우가 많습니다. 다운샘플링에서, 주어진 기간 내의 여러 데이터 요소는 단일한 것으로 축소됩니다. 서버 모니터링 예제에서, 데이터는 10초마다 측정되지만 대부분의 목적에 대해서는 1분 이내의 모든 값 평균으로 충분할 것입니다. 덧붙여 말하자면, 다운샘플링은 날짜 히스토그램 집계가 버킷당 하나 이상의 문서를 발견하는 경우 수행하는 바로 그 작업이며, 정확한 중첩 집계가 사용됩니다.

다음 예제는 다운샘플링의 첫 번째 예제로서 전체 1분 버킷에 걸쳐 중첩 avg, min, max 집계를 갖는 날짜 히스토그램 결과를 보여줍니다. fixed_interval 대신 calendar_interval를 사용하여 버킷 범위를 전체 분 수에 일치시킵니다.

예제 쿼리:

GET metricbeat-*/_search 
{ 
  "query": {...},  # same as above 
  "size": 0, 
  "aggregations": { 
    "myDateHistogram": { 
      "date_histogram": { 
        "field": "@timestamp", 
        "calendar_interval": "1m" 
      }, 
      "aggregations": { 
        "myDownsampledCpuUserMax": { 
          "max": { 
            "field": "system.cpu.user.pct" 
          } 
        }, 
        "myDownsampledCpuUserAvg": { 
          "avg": { 
            "field": "system.cpu.user.pct" 
          } 
        }, 
        "myDownsampledCpuUserMin": { 
          "min": { 
            "field": "system.cpu.user.pct" 
          } 
        } 
      } 
    } 
  } 
}

그리고 다음 예제 응답:

{ 
  ..., 
  "hits" : {...}, 
  "aggregations" : { 
    "myDateHistogram" : { 
      "buckets" : [ 
        { 
          "key_as_string" : "2019-08-12T13:27:00.000Z", 
          "key" : 1565616420000, 
          "doc_count" : 4, 
          "myDownsampledCpuUserMax" : { 
            "value" : 0.927 
          }, 
          "myDownsampledCpuUserMin" : { 
            "value" : 0.6980000000000001 
          }, 
          "myDownsampledCpuUserAvg" : { 
            "value" : 0.8512500000000001 
          } 
        }, 
        { 
          "key_as_string" : "2019-08-12T13:28:00.000Z", 
          "key" : 1565616480000, 
          "doc_count" : 6, 
          "myDownsampledCpuUserMax" : { 
            "value" : 0.838 
          }, 
          "myDownsampledCpuUserMin" : { 
            "value" : 0.5670000000000001 
          }, 
          "myDownsampledCpuUserAvg" : { 
            "value" : 0.7040000000000001 
          } 
        }, 
        ... 
      ] 
    } 
  } 
}

보시는 것처럼, myActualCpuUserMin, myActualCpuUserAvg, myActualCpuUserMax를 위한 값은 이제 다르며, 사용되는 집계에 따라 달라집니다.

다운샘플링에 어느 방법이 사용되는지는 메트릭에 따라 달라집니다. CPU 사용률의 경우, 1분에 걸친 avg 집계로 충분합니다. 큐 길이나 시스템 로드 같은 메트릭에 대해서는, max 집계가 더 적합할 수 있습니다.

이 시점에서는 또한 Elasticsearch를 사용해 일부 기본 산술을 수행하고 원래 데이터에 포함되지 않은 시계열을 계산하는 것도 가능합니다. CPU에 대해 avg 집계로 작업한다고 가정하면, 우리 예제는 다음과 같이 개선된 상태로 사용자 CPU, 시스템 CPU, 그리고 사용자 + 시스템의 합을 CPU 코어 수로 나눈 값을 반환할 수 있습니다.

예제 쿼리:

GET metricbeat-*/_search 
{ 
  "query": {...},   # same as above 
  "size": 0, 
  "aggregations": { 
    "myDateHistogram": { 
      "date_histogram": { 
        "field": "@timestamp", 
        "calendar_interval": "1m" 
      }, 
      "aggregations": { 
        "myDownsampledCpuUserAvg": { 
          "avg": { 
            "field": "system.cpu.user.pct" 
          } 
        }, 
        "myDownsampledCpuSystemAvg": { 
          "avg": { 
            "field": "system.cpu.system.pct" 
          } 
        }, 
        "myCpuCoresMax": { 
          "max": { 
            "field": "system.cpu.cores" 
          } 
        }, 
        "myCalculatedCpu": { 
          "bucket_script": { 
            "buckets_path": { 
              "user": "myDownsampledCpuUserAvg", 
              "system": "myDownsampledCpuSystemAvg", 
              "cores": "myCpuCoresMax" 
            }, 
            "script": { 
              "source": "(params.user + params.system) / params.cores", 
              "lang": "painless" 
            } 
          } 
        } 
      } 
    } 
  } 
}

그리고 다음 예제 응답:

{ 
  ..., 
  "hits" : {...}, 
  "aggregations" : { 
    "myDateHistogram" : { 
      "buckets" : [ 
        { 
          "key_as_string" : "2019-08-12T13:32:00.000Z", 
          "key" : 1565616720000, 
          "doc_count" : 2, 
          "myDownsampledCpuSystemAvg" : { 
            "value" : 0.344 
          }, 
          "myCpuCoresMax" : { 
            "value" : 8.0 
          }, 
          "myDownsampledCpuUserAvg" : { 
            "value" : 0.8860000000000001 
          }, 
          "myCalculatedCpu" : { 
            "value" : 0.15375 
          } 
        }, 
        { 
          "key_as_string" : "2019-08-12T13:33:00.000Z", 
          "key" : 1565616780000, 
          "doc_count" : 6, 
          "myDownsampledCpuSystemAvg" : { 
            "value" : 0.33416666666666667 
          }, 
          "myCpuCoresMax" : { 
            "value" : 8.0 
          }, 
          "myDownsampledCpuUserAvg" : { 
            "value" : 0.8895 
          }, 
          "myCalculatedCpu" : { 
            "value" : 0.15295833333333334 
          } 
        }, 
        ... 
      ] 
    } 
  } 
}

예제 2: 네트워크 트래픽 — 기간 및 파생 집계

Elasticsearch 집계가 시계열 데이터에 대해 얼마나 유용할 수 있는지를 보여주는 좀더 상세한 예제는 system.network 메트릭 세트입니다. system.network 메트릭 세트 문서의 관련 부분은 다음과 같이 보입니다.

{ 
... 
"system": { 
        "network": { 
            "in": { 
                "bytes": 37904869172, 
                "dropped": 32, 
                "errors": 0, 
                "packets": 32143403 
            }, 
            "name": "wlp4s0", 
            "out": { 
                "bytes": 6299331926, 
                "dropped": 0, 
                "errors": 0, 
                "packets": 13362703 
            } 
        } 
    } 
... 
}

Metricbeat는 시스템에 존재하는 모든 네트워크 인터페이스마다 하나의 문서를 전송하게 됩니다. 이러한 문서들은 동일한 타임스탬프를 갖게 되지만, 네트워크 인터페이스당 하나인 필드 system.network.name에 대해 다른 값을 갖게 됩니다.

완료된 모든 추가 집계는 인터페이스에 따라 이루어져야 하며, 따라서 우리는 이전 예제의 최상위 날짜 히스토그램 집계를 필드 system.network.name에 대한 기간 집계로 변경합니다.

이를 위해 집계의 기준이 되는 필드 작업은 키워드 필드로 매핑되어야 한다는 점에 유의하세요. Metricbeat이 제공하는 기본 인덱스 템플릿을 가지고 작업하는 경우, 이 매핑은 이미 사용자를 위해 설정되어 있습니다. 그렇지 않은 경우, Metricbeat 템플릿 문서 페이지에 무엇을 해야 하는지를 짤막하게 설명해 놓았으니 참조하시기 바랍니다.

예제 쿼리:

GET metricbeat-*/_search 
{ 
  "query": {...}, # same as above 
  "size": 0, 
  "aggregations": { 
    "myNetworkInterfaces": { 
      "terms": { 
        "field": "system.network.name", 
        "size": 50 
      }, 
      "aggs": { 
        "myDateHistogram": { 
          "date_histogram": { 
            "field": "@timestamp", 
            "calendar_interval": "1m" 
          } 
        } 
      } 
    } 
  } 
}

그리고 다음 예제 응답:

{ 
  ..., 
  "hits" : {...}, 
  "aggregations" : { 
    "myNetworkInterfaces" : { 
      "doc_count_error_upper_bound" : 0, 
      "sum_other_doc_count" : 0, 
      "buckets" : [ 
        { 
          "key" : "docker0", 
          "doc_count" : 29, 
          "myDateHistogram" : { 
            "buckets" : [...] 
          } 
        }, 
        { 
          "key" : "enp0s31f6", 
          "doc_count" : 29, 
          "myDateHistogram" : { 
            "buckets" : [...] 
          } 
        }, 
        { 
          "key" : "lo", 
          "doc_count" : 29, 
          "myDateHistogram" : { 
            "buckets" : [...] 
          } 
        }, 
        { 
          "key" : "wlp61s0", 
          "doc_count" : 29, 
          "myDateHistogram" : { 
            "buckets" : [ 
              { 
                "key_as_string" : "2019-08-12T13:39:00.000Z", 
                "key" : 1565617140000, 
                "doc_count" : 1 
              }, 
              { 
                "key_as_string" : "2019-08-12T13:40:00.000Z", 
                "key" : 1565617200000, 
                "doc_count" : 6 
              }, 
              { 
                "key_as_string" : "2019-08-12T13:41:00.000Z", 
                "key" : 1565617260000, 
                "doc_count" : 6 
              }, 
              { 
                "key_as_string" : "2019-08-12T13:42:00.000Z", 
                "key" : 1565617320000, 
                "doc_count" : 6 
              }, 
              { 
                "key_as_string" : "2019-08-12T13:43:00.000Z", 
                "key" : 1565617380000, 
                "doc_count" : 6 
              }, 
              { 
                "key_as_string" : "2019-08-12T13:44:00.000Z", 
                "key" : 1565617440000, 
                "doc_count" : 4 
              } 
            ] 
          } 
        }, 
        ... 
      ] 
    } 
  } 
}

CPU 예제에서처럼, 중첩 집계가 없는 경우 날짜 히스토그램 집계는 doc_count만을 반환하며, 이것은 대단히 유용하지 않습니다.

바이트를 위한 필드에는 일정하게 증가하는 값이 포함됩니다. 이러한 필드의 값에는 마지막으로 컴퓨터가 시작된 이래 전송되거나 수신된 바이트의 수가 포함되며, 따라서 측정할 때마다 증가합니다. 이 경우에는, 정확한 중첩 집계가 max입니다. 따라서 다운샘플링된 값에는 버킷 간격 동안 측정된 최고 측정치, 따라서 최신 측정치가 포함됩니다.

예제 쿼리:

GET metricbeat-*/_search 
{ 
  "query": {...},  # same as above 
  "size": 0, 
  "aggregations": { 
    "myNetworkInterfaces": { 
      "terms": { 
        "field": "system.network.name", 
        "size": 50 
      }, 
      "aggs": { 
        "myDateHistogram": { 
          "date_histogram": { 
            "field": "@timestamp", 
            "calendar_interval": "1m" 
          }, 
          "aggregations": { 
            "myNetworkInBytesMax": { 
              "max": { 
                "field": "system.network.in.bytes" 
              } 
            }, 
            "myNetworkOutBytesMax": { 
              "max": { 
                "field": "system.network.out.bytes" 
              } 
            } 
          } 
        } 
      } 
    } 
  } 
}

그리고 다음 예제 응답:

{ 
  ..., 
  "hits" : {...}, 
  "aggregations" : { 
    "myNetworkInterfaces" : { 
      "doc_count_error_upper_bound" : 0, 
      "sum_other_doc_count" : 0, 
      "buckets" : [ 
        { 
          "key" : "docker0", 
          ... 
        }, 
        { 
          "key" : "enp0s31f6", 
          ... 
        }, 
        { 
          "key" : "lo", 
          ... 
        }, 
        { 
          "key" : "wlp61s0", 
          "doc_count" : 30, 
          "myDateHistogram" : { 
            "buckets" : [ 
              { 
                "key_as_string" : "2019-08-12T13:50:00.000Z", 
                "key" : 1565617800000, 
                "doc_count" : 2, 
                "myNetworkInBytesMax" : { 
                  "value" : 2.991659837E9 
                }, 
                "myNetworkOutBytesMax" : { 
                  "value" : 5.46578365E8 
                } 
              }, 
              { 
                "key_as_string" : "2019-08-12T13:51:00.000Z", 
                "key" : 1565617860000, 
                "doc_count" : 6, 
                "myNetworkInBytesMax" : { 
                  "value" : 2.992027006E9 
                }, 
                "myNetworkOutBytesMax" : { 
                  "value" : 5.46791988E8 
                }, 
                "myNetworkInBytesPerSecond" : { 
                  "value" : 367169.0, 
                  "normalized_value" : 6119.483333333334 
                }, 
                "myNetworkoutBytesPerSecond" : { 
                  "value" : 213623.0, 
                  "normalized_value" : 3560.383333333333 
                } 
              }, 
              ... 
            ] 
          } 
        }, 
        ... 
      ] 
    } 
  } 
}

일정하게 증가하는 카운터로부터 초당 바이트의 비율을 얻기 위해, 파생 집계가 사용될 수 있습니다. 선택 사항인 매개변수 unit이 이 집계로 전달되면, 다음과 같이 normalized_value 필드에서 단위당 원하는 값을 반환합니다.

예제 쿼리:

GET metricbeat-*/_search 
{ 
  "query": {...},  # same as above 
  "size": 0, 
  "aggregations": { 
    "myNetworkInterfaces": { 
      "terms": { 
        "field": "system.network.name", 
        "size": 50 
      }, 
      "aggs": { 
        "myDateHistogram": { 
          "date_histogram": { 
            "field": "@timestamp", 
            "calendar_interval": "1m" 
          }, 
          "aggregations": { 
            "myNetworkInBytesMax": { 
              "max": { 
                "field": "system.network.in.bytes" 
              } 
            }, 
            "myNetworkInBytesPerSecond": { 
              "derivative": { 
                "buckets_path": "myNetworkInBytesMax", 
                "unit": "1s" 
              } 
            }, 
            "myNetworkOutBytesMax": { 
              "max": { 
                "field": "system.network.out.bytes" 
              } 
            }, 
            "myNetworkoutBytesPerSecond": { 
              "derivative": { 
                "buckets_path": "myNetworkOutBytesMax", 
                "unit": "1s" 
              } 
            } 
          } 
        } 
      } 
    } 
  } 
}

그리고 다음 예제 응답:

{ 
  ..., 
  "hits" : {...}, 
  "aggregations" : { 
    "myNetworkInterfaces" : { 
      "doc_count_error_upper_bound" : 0, 
      "sum_other_doc_count" : 0, 
      "buckets" : [ 
        { 
          "key" : "docker0", 
          ... 
        }, 
        { 
          "key" : "enp0s31f6", 
          ... 
        }, 
        { 
          "key" : "lo", 
          ... 
        }, 
        { 
          "key" : "wlp61s0", 
          "doc_count" : 30, 
          "myDateHistogram" : { 
            "buckets" : [ 
              { 
                "key_as_string" : "2019-08-12T14:07:00.000Z", 
                "key" : 1565618820000, 
                "doc_count" : 4, 
                "myNetworkInBytesMax" : { 
                  "value" : 3.030494669E9 
                }, 
                "myNetworkOutBytesMax" : { 
                  "value" : 5.56084749E8 
                } 
              }, 
              { 
                "key_as_string" : "2019-08-12T14:08:00.000Z", 
                "key" : 1565618880000, 
                "doc_count" : 6, 
                "myNetworkInBytesMax" : { 
                  "value" : 3.033793744E9 
                }, 
                "myNetworkOutBytesMax" : { 
                  "value" : 5.56323416E8 
                }, 
                "myNetworkInBytesPerSecond" : { 
                  "value" : 3299075.0, 
                  "normalized_value" : 54984.583333333336 
                }, 
                "myNetworkoutBytesPerSecond" : { 
                  "value" : 238667.0, 
                  "normalized_value" : 3977.7833333333333 
                } 
              }, 
              { 
                "key_as_string" : "2019-08-12T14:09:00.000Z", 
                "key" : 1565618940000, 
                "doc_count" : 6, 
                "myNetworkInBytesMax" : { 
                  "value" : 3.037045046E9 
                }, 
                "myNetworkOutBytesMax" : { 
                  "value" : 5.56566282E8 
                }, 
                "myNetworkInBytesPerSecond" : { 
                  "value" : 3251302.0, 
                  "normalized_value" : 54188.36666666667 
                }, 
                "myNetworkoutBytesPerSecond" : { 
                  "value" : 242866.0, 
                  "normalized_value" : 4047.766666666667 
                } 
              }, 
              ...   
            ] 
          } 
        }, 
        ... 
      ] 
    } 
  } 
}

자체 클러스터에서 이 모든 것을 직접 해보실 수 있습니다. 아직 클러스터가 없는 경우, Elastic Cloud의 Elasticsearch Service 무료 체험판을 사용하거나 Elastic Stack의 기본 배포를 다운로드받으실 수 있습니다. Metricbeat로 시스템에서 데이터 전송을 시작하고 쿼리 작업을 해보세요!