본문 바로가기
ETC DB

prometheus 데이터 구조

by 타마마임팩트_쫀 2021. 7. 21.

1. TSDB의 읽기 쓰기

일반적인 TSDB 데이터베이스를 살펴 보면 세로 축은 시계열, 가로 축은 타임 스탬프가 있는 sample 시퀀스입니다.
Prometheus에는 일반적으로 샘플 데이터가 수백만 개가 있으며 기간은 몇 주 단위입니다.

쓰기 : 짧은 시간에 많은 시계열에 sample을 추가합니다. 일반적으로 쓰기만 사용합니다.
         그러나 어느 시점에서 이전 데이터를 자르거나 download 할 수 있습니다.
         위 예에서는 빨간 부분의 세로로 사용.

읽기 : 상대적으로 긴 시간 동안 상대적으로 적은 시계열 (가장 일반적으로 하나)에서 sample을 읽습니다.
         이는 쓰기 패턴과 정확히 수직이므로 TSDB를 올바르게 설정하기가 어렵습니다.
         예외가 있지만 대체로 많은 비용이 드는 쿼리는 시계열에 따른 쿼리입니다.
         위 예에서는 녹색 부분의 가로로 사용.

*sample : 수집된 데이터

 

2. prometheus 내부 저장소

prometheus는 in-memory에 자체 sample storage 레이어를 사용합니다.
그리고 하위 저장소로 파일 시스템과 Level DB를 사용합니다. (초기 버전에서는 sample 저장에도 Level DB를 사용)
Level DB는 Google에서 만든 light-weight의 Key-value 저장소이며, 전반적으로 뛰어난 성능을 보이기에 여러곳에서 사용합니다.
Level DB는 기본적으로 압축을 지원합니다.

 

3. prometheus 데이터 구조

시계열 데이터를 처리하기위한 기본 데이터 구조는 다음과 같이 정의 됩니다.

type sample struct {
        Labels map[string]string
        Value  float64
}

 

prometheus의 메트릭은 다음과 같이 수집됩니다.

메트릭명{필드1=값, 필드2=값} 샘플링데이터

 

아래는 prometheus export 라이브러리를 붙인 모습입니다.

많은 수의 metric이 발생을 하게 되고, 이를 text/html 방식으로 특정 url(대부분 /metrics)로 export를 해두게 되면, prometheus 서버가 이를 긁어가서 데이터를 저장합니다.

metric 이름이 제일 먼저 나오고, metric의 특징을 표현하는 레이블(label)들이 있습니다. 그리고 가장 마지막으로는 metric 값(value)이 있습니다.
필요에 따라 timestamp도 표시될 수 있다. timestamp를 보통 출력하지 않는데, 그 이유는 수집하는 순간의 시간으로 값이 기록되기 때문입니다.

 

4. sample 저장소

Prometheus sample 저장소가 작동하는 방식은 아래와 같습니다.

sample 데이터는 일정한 크기의 chunk로 구성됩니다.

새로 수집된 sample은 head chunk에 기록되며, LRU 알고리즘에 의해 memory에서 disk로 chunk를 이동시킵니다.
원래는 freelist 나 거대한 메모리에 모두 보관했지만, fragmentation 방지를 위해 memory에 일정한 크기를 할당하고 할당 해제를 반복합니다.

disk의 chunk 표현은 메모리와 정확히 동일하며 1 : 1 대응입니다.
PromQL 쿼리는 전적으로 메모리에서 제공됩니다.

입력, 네트워크 페이로드, 구문 분석 결과 및 쿼리에 필요한 개별 값 출력 중에 만 disk, memory 변환이 발생하며 각 sample은 메모리에 chunk를 가집니다.

*chunk : 데이터 저장 단위

 

5. series의 사이클과 유지보수

아직 disk에 없는 전체 청크를 추가합니다.
retention 시간보다 오래된 청크는 삭제됩니다.
위 작업이 실제로는 series의 파일을 다시 작성하는 것을 의미합니다.

 

6. chunk preloading

쿼리에는 현재 메모리에 없는 chunk가 필요할 수 있습니다.
쿼리 엔진은 쿼리 시간 동안 chunk를 disk에서 memory로 미리 로드합니다.

 

7. checkpointing

종료나 장애시 데이터 손상을 줄이기 위해 disk의 series 파일에 유지되지 않는 chunk는 정기적으로 파일로 check point 됩니다.
그리고 시작하는 동안 다시 memory로 load 됩니다.

 

8. prometheus data type

Prometheus에서 Data Type은 4가지로 분류됩니다.
위에서 언급한 Metric 값(value)의 타입을 말하는 것이 아닙니다.
Prometheus에서 사용하는 값의 타입은 오로지 float64 입니다.

Instant vector - 각 시계열에 대한 단일 샘플을 포함하는 시계열 집합이며 모두 동일한 타임 스탬프를 공유 합니다.
Range vector - 각 시계열에 대한 시간 경과에 따른 데이터 포인트 범위를 포함하는 시계열 세트 입니다.
Scalar - 간단한 숫자 부동 소수점 값 입니다.
String - 간단한 문자열 값. 현재 미사용 합니다.

 

위 4가지 타입의 설명만으로는 이해가 안될 수 있기에 추가적인 설명을 하도록 합니다.
Time series란 시계열 이란 의미로 시간변화에 따른 값이 변화를 말합니다. 우리가 보고자 하는 metric 데이터는 시간에 따른 값의 변화를 나타내는데, [시간, 값]...[시간, 값] 이런식으로 표현이 됩니다.예를 들면, container A의 CPU 값은 [1분, 0.1], [2분, 0.2], [3분, 0.1] 과 같이 나타낼 수 있습니다. 이런 배열들을 묶어서 하나의 Time-series라 하는 것이다. 여기서 1개 요소인 [1분, 0.1] 을 Sample이라 합니다.

http_requests_total{method="POST", handler="/messages"} 1037
http_requests_total{method="GET", handler="/messages"} 500


위 예를 보면, 두개의 Sample이 존재하고, 시간의 변화에 따라 기록되는 Sample들의 묶음(배열)이 바로 Time-series라고 생각 하면 됩니다.

 

8.1. Instant Vector

Instant vector는 여러 time-series에서 같은 시간대를 가리키는 Sample 집합을 말합니다.
위와 같이 http_requests_total에 대해 2가지 time-series가 존재하는데 같은 시간에 해당되는 2개의 Sample 집합을 Instant vector라 합니다.
비유를 좀 하자면 Time-series가 시간에 따른 가로축을 표현했다라고 하면, Instant Vector는 Time-series를 세로로 자른 단면 같다고 할 수 있습니다. (처음 언급한 그래프)
기본적으로 단순히 metric 이름으로 쿼리를 실행하면 Instance Vector를 얻을 수 있고, 필터링을 하고 싶으면 Instant Vector selector를 사용하면 됩니다.

 

8.2. Range Vector

Range vetor는 특정 시간동안(period)의 값들을 배열로 가진 타입입니다.
Instant vector는 각 time-series들이 1개의 Sample만을 가지는 타입인데, Range vector은 각 Time-series들이 주어진 기준 시간부터 명시된 과거 시간사이의 모든 값들을 가질 수 있습니다.
Range Vector 표시는 metric 이름에 대괄호 [<period>] 를 붙이면 됩니다.

예를들어 http_requests_total[5m] 로 쿼리를 실행해보면 다음과 같이 나옵니다.

http_requests_total{method="POST", handler="/messages"}[5m]
[1037 @1551242271.728, 1038 @1551242331.728, 1040 @1551242391.728]
http_requests_total{method="GET", handler="/messages"}[5m]
[500 @1551242484.013, 501 @1551242544.013, 502 @1551242604.013]

배열 요소로 <값>@<timestamp> 형태로 들어가 있다. 위 예시는 5분 동안에 3개의 값이 존재하고 있습니다.

이렇게 값이 1개가 아닌, 여러개가 배열로 표현됩니다.

 

8.3. Range Query와 Range vector

위의 range vector 결과만으로 그래프를 그릴 수 없습니다. 그래프를 그리려면 정확한 1개의 값으로 만들어 그려야 하는데 여기서는 3개가 존재하기 때문입니다.

여기서 Range Query와 Range vector를 혼동할 수 있을 것 같아 이에 대해 설명하자면,
Range Query는 Start 와 End 시간을 옵션으로 주는 쿼리 표현 종류(API에 비유)이고, Range vector는 데이터 타입입니다.

쿼리를 실행해서 그래프를 그리기 위해서는 Instant vector + Range Query로 실행되어야 합니다.

예시로 설명한 http_requests_total로 쿼리를 실행 했을 때 최근 5분의 값만 보여주게 됩니다.
이때는 Range Query로 실행되는게 아닌 Instant Query로 실행됩니다. 그래프로 그리기 위해서는 보려는 Start-End시간 사이의 모든 Instant vector의 값들이 필요합니다.

Range vector로 그래프를 그릴 수 없다면, Range vector의 결과를 aggregation하여 1개의 Instant vector로 만들어 주는 aggregation function을 사용해야 합니다. 

다음은 대표적인 function인 rate를 이용하여 http_requests_total로 쿼리를 변환한 예 입니다.

rate(http_requests_total{method="POST", handler="/messages"}[5m])
0.01098

rate 함수는 1초당 변화량으로 환산하는 함수로, 앞선 예시에서 5분 동안에 포함되는 3개의 값의 변화를 초당으로 변환해 1개의 값으로 나타 냅니다.

aggregation function없이 range vector로만 가지고 그래프로 표현하려고 하면 에러가 발생합니다.

Invalid expression type "range vector" for range query, must be Scalar or instant Vector 라는 에러가 발생합니다.

 

8.4. Instant Query

Instant Query는 다음 API로 호출합니다.

GET /api/v1/query
POST /api/v1/query

다음은 Grafana에서 기본 Query로 실행 할때의 API 호출 예 입니다.

start, end가 아닌 특정 시간(time)이 query string으로 들어가 있습니다. resultType은 vector 입니다.

 

다음은 up 쿼리에 대한 2015-07-01T20:10:51.781 시간의 Instant Query 예제 입니다.

$ curl 'http://localhost:9090/api/v1/query?query=up&time=2015-07-01T20:10:51.781Z'
{
   "status" : "success",
   "data" : {
      "resultType" : "vector",
      "result" : [
         {
            "metric" : {
               "__name__" : "up",
               "job" : "prometheus",
               "instance" : "localhost:9090"
            },
            "value": [ 1435781451.781, "1" ]
         },
         {
            "metric" : {
               "__name__" : "up",
               "job" : "node",
               "instance" : "localhost:9100"
            },
            "value" : [ 1435781451.781, "0" ]
         }
      ]
   }
}

resultType은 vector로 해당 타임 스탬프와 값을 확인 할 수 있습니다. (instant query + instant vector)

 

8.5. Range Query

Range Query는 다음 API로 호출합니다.

GET /api/v1/query_range
POST /api/v1/query_range

다음은 Grafana에서 Range Query로 실행할때의 API호출 예 입니다.

API가 다르고 start, end가 query string 으로 들어간 것을 알 수 있습니다. resultType은 matrix입니다. (시간대 별로 vector값이 들어간 내부적으로 쓰이는 복합 타입)

 

다음은 up 쿼리에 대해 2015-07-01T20:10:30.781 시간 부터 2015-07-01T20:11:00.781 시간 까지 15초 단위의 Range Query 예제 입니다.

$ curl 'http://localhost:9090/api/v1/query_range?query=up&start=2015-07-01T20:10:30.781Z&end=2015-07-01T20:11:00.781Z&step=15s'
{
   "status" : "success",
   "data" : {
      "resultType" : "matrix",
      "result" : [
         {
            "metric" : {
               "__name__" : "up",
               "job" : "prometheus",
               "instance" : "localhost:9090"
            },
            "values" : [
               [ 1435781430.781, "1" ],
               [ 1435781445.781, "1" ],
               [ 1435781460.781, "1" ]
            ]
         },
         {
            "metric" : {
               "__name__" : "up",
               "job" : "node",
               "instance" : "localhost:9091"
            },
            "values" : [
               [ 1435781430.781, "0" ],
               [ 1435781445.781, "0" ],
               [ 1435781460.781, "1" ]
            ]
         }
      ]
   }
}

resultType은 matrix으로 각 시간별로 타임스탬프와 값을 확인 할 수 있습니다. (range query + instant vector)

 

'ETC DB' 카테고리의 다른 글

prometheus TSDB 관리 API  (0) 2021.07.21
prometheus disk storage  (0) 2021.07.21
prometheus 개요  (0) 2021.07.21
Amazon Aurora DB와 mysql 차이점  (0) 2021.07.21
Amazon Aurora 특징  (0) 2021.07.21