04 11월 2015 사용자 스토리

시계열 데이터 스토어로서의 Elasticsearch

By Felix Barnsteiner

저는 오픈 소스 성능 모니터링 도구인 stagemonitor, 의 프로젝트 관리자로서, 백엔드로서 여전히 멋지지만 노후화되고 있는 TSDB(Graphite Time Series DataBase)를 대체할 데이터베이스를 찾고 있었습니다. TSDB는 앱 응답 시간 또는 서버의 CPU 활용과 같은 (성능) 메트릭 데이터를 저장할 수 있는 특별한 패키지입니다. 궁극적으로는 설치가 용이하고 확장 가능하며 다양한 기능을 지원하고 메트릭 시각화를 강력하게 지원하는 데이터스토어를 찾고 있었습니다.

과거 Elasticsearch와의 업무 협력 경험에 비추어 설치가 용이하고 확장 가능하며 많은 aggregation을 제공할 뿐 아니라 Kibana에 훌륭한 시각화 도구를 보유하고 있다는 사실을 알고 있었지만 Elasticsearch가 시계열 데이터에 적합한지 여부는 알지 못했습니다. 이는 많은 이들의 의문점이었으며 실제로 CERN (유럽 원자핵 공동 연구소)이 Elasticsearch, InfluxDB, OpenTSDB 간의 성능 비교 를 수행하여 Elasticsearch를 그 중 1위로 발표했습니다.

의사 결정 프로세스

Elasticsearch는 구조적 데이터와 비구조적 데이터(자유로운 텍스트, 시스템 로그, 데이터베이스 레코드 등)를 저장, 검색 및 분석할 수 있는 훌륭한 도구입니다. 또한 올바른 수정으로 Collectd 또는 Statsd와 같은 도구에서 시계열 메트릭을 저장할 수 있는 우수한 플랫폼을 얻을 수 있습니다.

뿐만 아니라 메트릭 추가에 따른 확장이 매우 용이합니다. Elasticsearch는 샤드 복제로 기본적인 중복 성능을 보유하고 있으며 cluster 및 데이터에 대한 관리 용이성을 크게 향상시키는 스냅샷 및 복원 기능으로 백업을 간소화할 수 있습니다.

Elasticsearch는 또한 API 주도적이며 Logstash와 같은 통합 도구를 사용하여 대용량 데이터를 효율적으로 처리할 수 있는 데이터 처리 파이프라인을 쉽게 구축할 수 있습니다. 여기에 Kibana를 추가함으로써 여러 데이터세트를 수집, 분석하고 메트릭과 기타 데이터로부터 함께 상관 관계를 도출할 수 있는 플랫폼을 갖게 됩니다.

곧바로 명확히 나타나지 않는 또 다른 이점으로는 그래프로 표시되는 엔드포인트 값을 제공하기 위해 계산을 통해 변환된 메트릭을 저장하지 않고 원시 값을 저장한 다음 해당 값에 대해 Elasticsearch에 빌드된 강력한 어그리케이션을 실행한다는 것입니다. 이는 메트릭을 모니터링하고 몇 달 후 생각이 바뀌어 메트릭을 다르게 계산 또는 표시하려는 경우 과거 및 현재 데이터 모두 데이터세트에 대한 aggregation만 변경하면 된다는 것을 의미합니다. 다시 말해 데이터 저장 시점에 생각하지 못한 질문을 제시하거나 질문에 답변할 수 있게 된다는 것입니다.

이를 모두 고려할 때 해답이 필요한 명확한 질문은 "Elasticsearch를 시계열 데이터베이스로 설정하기 위한 가장 좋은 방법은 무엇인가"하는 것입니다.

첫 번째 단계: 매핑

시작 단계에서 가장 중요한 것은 매핑입니다. 매핑을 미리 정의한다는 것은 Elasticsearch에서의 데이터 분석 및 저장이 최대한 최적화된다는 것을 의미합니다.

Stagemonitor의 매핑 수행 방법 예를 소개합니다. 원본은 stagemonitor 깃헙(GitHub) 보고서 에서도 확인할 수 있습니다.

{
  "template": "stagemonitor-metrics-*",
  "settings": {
    "index": {
      "refresh_interval": "5s"
    }
  },
  "mappings": {
    "_default_": {
      "dynamic_templates": [
        {
          "strings": {
            "match": "*",
            "match_mapping_type": "string",
            "mapping":   { "type": "string",  "doc_values": true, "index": "not_analyzed" }
          }
        }
      ],
      "_all":            { "enabled": false },
      "_source":         { "enabled": false },
      "properties": {
        "@timestamp":    { "type": "date",    "doc_values": true },
        "count":         { "type": "integer", "doc_values": true, "index": "no" },
        "m1_rate":       { "type": "float",   "doc_values": true, "index": "no" },
        "m5_rate":       { "type": "float",   "doc_values": true, "index": "no" },
        "m15_rate":      { "type": "float",   "doc_values": true, "index": "no" },
        "max":           { "type": "integer", "doc_values": true, "index": "no" },
        "mean":          { "type": "integer", "doc_values": true, "index": "no" },
        "mean_rate":     { "type": "float",   "doc_values": true, "index": "no" },
        "median":        { "type": "float",   "doc_values": true, "index": "no" },
        "min":           { "type": "float",   "doc_values": true, "index": "no" },
        "p25":           { "type": "float",   "doc_values": true, "index": "no" },
        "p75":           { "type": "float",   "doc_values": true, "index": "no" },
        "p95":           { "type": "float",   "doc_values": true, "index": "no" },
        "p98":           { "type": "float",   "doc_values": true, "index": "no" },
        "p99":           { "type": "float",   "doc_values": true, "index": "no" },
        "p999":          { "type": "float",   "doc_values": true, "index": "no" },
        "std":           { "type": "float",   "doc_values": true, "index": "no" },
        "value":         { "type": "float",   "doc_values": true, "index": "no" },
        "value_boolean": { "type": "boolean", "doc_values": true, "index": "no" },
        "value_string":  { "type": "string",  "doc_values": true, "index": "no" }
      }
    }
  }
}

이 경우 aggregation 빌드만 수행하기 위해 _source_all 을 비활성화했으므로 저장된 문서가 크지 않아 디스크 공간을 절약할 수 있습니다. 단점은 실제 JSON 문서를 보거나 새 매핑으로 재인덱싱하거나 구조를 인덱싱할 수 없다는 것입니다(자세한 내용은 _source 비활성화 문서 참조). 그러나 Elasticsearch의 메트릭 기반 사용 사례의 경우에는 크게 걱정할 필요가 없는 문제입니다.

주의: 대부분의 경우에는 _source를 비활성화하지 않습니다!

또한 메트릭 문서에 대한 전체 텍스트 검색을 수행하지 않으므로 문자열 값을 분석하지 않습니다. 이 경우 정확한 이름으로 필터링하거나 metricName, 호스트 또는 애플리케이션 에 대한 용어 aggregation만 수행하면 되므로 특정 호스트로 메트릭을 필터링하거나 모든 호스트에 대한 목록을 얻을 수 있습니다. 더불어 doc_values를 최대한 많이 사용하여 힙 사용을 줄이는 것이 좋습니다.

T일부 사례에만 적합한 보다 공격적인 두 가지 최적화 방법이 있습니다. 첫 번째 방법은 모든 메트릭 값에 "index": "no" 를 사용하는 것입니다. 이 방법을 사용하면 인덱스 크기는 줄어들지만 값을 검색할 수 없으므로, 모든 값을 2.7182와 3.1415 사이의 값과 같이 서브 세트뿐 아니라 그래프로 표시하려는 경우 적합합니다. 가장 작은 숫자 유형을 사용함으로써(당사의 경우 float) 인덱스를 더 최적화할 수 있습니다. 케이스 요청 값이 float 범위를 벗어나는 경우에는 doubles를 사용할 수 있습니다.

다음 방법: 장기 보관을 위한 최적화

이 데이터를 장기 보관 목적으로 최적화하는 데 있어 중요한 다음 단계는 모든 데이터가 인덱싱된 후 인덱스를 강제로 merge(최적화 - optimize)하는 것입니다. 여기에는 기존 샤드를 소수로 병합하고 동일한 단계에서 삭제된 문서를 제거하는 작업이 포함됩니다. 이전 개념의 "최적화"는 Elasticsearch에서 다소 오해의 소지가 있는 용어였습니다. 즉, 리소스 사용을 개선하지만 시스템이 삭제 표기한 문서를 영구 삭제한 다음 모든 기본 Lucene 세그먼트를 병합하여 CPU 및 디스크 리소스가 많이 필요한 프로세스였습니다. 강제 merge를 시스템 사용이 적은 시간이나 CPU 및 디스크 리소스가 많은 노드에서 실행하도록 권장하는 이유가 바로 이 때문입니다.

Merge 프로세스는 백그라운드에서 자동으로 실행되지만 index에 데이터가 기록되고 있는 동안에만 실행됩니다. 이 프로세스는 모든 이벤트가 Elasticsearch로 전송되어 추가, 업데이트 또는 삭제에 따라 index가 더 이상 수정되지 않는 것으로 확인한 후 한 번만 명시적으로 호출해야 합니다.

최적화는 일반적으로 지연된 이벤트가 Elasticsearch에 도달할 수 있도록 최신 index가 생성된 후(매시간, 매일, 매주 등) 24~48시간까지 유지되는 것이 가장 좋습니다. 이 기간 이후에는 Curator 를 사용하여 최적화 호출을 쉽게 처리할 수 있습니다.

$ curator optimize --delay 2 --max_num_segments 1 indices --older-than 1 --time-unit days --timestring %Y.%m.%d --prefix stagemonitor-metrics-

모든 데이터가 기록된 후 이 최적화를 실행하는 데 대한 또 다른 큰 이점은 cluster 복구 속도로 지원되는 동기화된 flush 와 노드 재시작을 자동으로 적용하는 것입니다.

Stagemonitor를 사용하는 경우에는 최적화 프로세스가 매일 밤 자동으로 트리거되므로 이러한 경우에도 curator를 사용하지 않아도 됩니다.

결과

이 테스트를 수행하기 위해 대략적으로 일주일 분량에 해당하는 2천3백만 개가 넘는 데이터 포인트를 무작위로 stagemonitor 플랫폼에서 Elasticsearch으로 보냈습니다. 데이터 샘플은 다음과 같습니다.

{
    "@timestamp": 1442165810,
"name": "timer_1",
    "application": "Metrics Store Benchmark",
    "host": "my_hostname",
    "instance": "Local",
    "count": 21,
    "mean": 714.86,
    "min": 248.00,
    "max": 979.00,
    "stddev": 216.63,
    "p50": 741.00,
    "p75": 925.00,
    "p95": 977.00,
    "p98": 979.00,
    "p99": 979.00,
    "p999": 979.00,
    "mean_rate": 2.03,
    "m1_rate": 2.18,
    "m5_rate": 2.20,
    "m15_rate": 2.20
}

몇몇 인덱싱 및 최적화 사이클을 실행한 후 다음과 같은 결과 수치를 얻었습니다.

  최초 크기 P최적화 후
샘플 실행 1 2.2G 508.6M
샘플 실행 2 514.1M
샘플 실행 3 510.9M
샘플 실행 4 510.9M
샘플 실행 5 510.9M

위 결과로 최적화 프로세스의 중요성을 확인할 수 있습니다. Elasticsearch가 이 작업을 백그라운드에서 실행한다고 해도, 장기 보관 목적만으로도 실행할 가치가 충분히 있습니다.

Elasticsearch의 이 모든 데이터를 고려할 때 어떤 사실을 발견할 수 있습니까? 시스템에서 작성한 몇 가지 샘플을 확인해보도록 하겠습니다.

stagemonitor-applications-metrics.png
stagemonitor-hosts-metrics.png
stagemonitor-instances-metrics.png
stagemonitor-response-times.png
stagemonitor-throughputstatuscode-line.png
stagemonitor-toprequests-metric.png
stagemonitor-pageloadtime-line.png
stagemonitor-slowestrequestsmedian-line.png
stagemonitor-highestthroughput-line.png
stagemonitor-slowestrequests-line.png
stagemonitor-mosterrors-line.png

여기서 수행한 테스트를 복제하려면 stagemonitor 깃헙(GitHub) 보고서의 코드를 활용하면 됩니다.

전망

Elasticsearch 2.0은 시계열 데이터 사용자에게 보다 적합하고 보다 향상된 유연성을 제공할 수 있는 많은 기능을 보유하고 있습니다.

Pipeline aggregations 은 새로운 차원의 데이터 포인트 분석 및 변환 기능을 제공합니다. 예를 들어 평균 이동으로 그래프를 고르게 만들거나 Holt Winters 예측 기능을 사용하여 데이터가 이전 패턴과 일치하는지 여부를 확인할 수 있으며 편차까지 계산할 수 있습니다.

마지막으로, 앞에서 설명한 매핑에서는 힙 효율성 향상을 위해 doc_values를 수동으로 활성화해야 했습니다. 2.0에서는 분석되지 않은 모든 필드에 대해 기본적으로 doc_value가 활성화되므로 작업이 간소화되었습니다!

작성자 소개 — Felix Barnsteiner

felix-barsteiner.jpegFelix Barnsteiner는 오픈 소스 성능 모니터링 프로젝트인 stagemonitor의 개발자로서 현재는 독일 뮌헨 소재 iSYS Software GmbH에서 eCommerce 솔루션을 연구하고 있습니다.