Monitoring

Prometheus TSDB 분석

토마스.dev 2024. 1. 28. 12:04

https://ganeshvernekar.com/blog/prometheus-tsdb-the-head-block/

 

Prometheus TSDB (Part 1): The Head Block | Ganesh Vernekar

Walk-through on how the in-memory part of Prometheus TSDB works

ganeshvernekar.com

위 블로그를 바탕으로 좀 더 이해가 될수 있도록 추가 분석한 내용을 정리한다.

 

프로메테우스는 자체 TSDB(TimeSeries DataBase)를 가지고 있다. DB의 기본적인 특성을 가지고 있으면서도 시계열에 특화된 구조로 되어있다. TSDB의 기본적인 구조는 다음과 같다.

 

Prometheus TSDB 구조 개념

Sample

(t,v): (timestamp, value) 이 하나의 값을 Sample이라 부른다. 이 sample 들이 모이는 최소한의 그룹을 Chunk라 한다.
Block은 Chunk 들의 모임이다. 자세히 보면 Block N은 Block 1,2보다 긴것(큰것)으로 표시된다. 이는 Block크기가 시간이 흐르면서 병합과정을 거치기 때문에 더 커질 수 있음을 의미한다.

 

Head Chunk vs Block Chunk

Head Chunk는 메모리와 디스크에 저장된(하지만 mmap된) 것을 말하고, Block Chunk는 오로지 디스크에만 저장된 것을 말한다.

 

Series

time-series를 말하는 것은 아니고, 여기서 사용되는 키워드이다. 심볼(metric{container="xxx", pod="yyy"} 라고 하면, metric, container, xxx, pod, yyy 이 심볼)과 레이블 정보(k=v)를 series라고 한다. Sample 들을 묶어놓고 그 앞단에 series ref가 있다고 생각하면 된다.

WAL(Write-Ahead-Log)

최신의 데이터는 메모리에 들고 있다. 그러면 갑작스런 중단등으로 인해 그 데이터가 소실될 수 있는데, 이를 복구하고자 로그를 기록한다. 말그대로 즉시즉시 기록하는 로그이기 때문에 압축등을 하지 않아 크기가 크다. Write Amplification 을 막기 위해 32kb 단위로 쓰여지고 최대 128mb 까지 적재된다고 한다.

 

M-map chunks: Memory-Mapped Chunks

원래는 head 를 전부 메모리로만 들고 있었는데, 2.19 버전부터 이 컨셉이 도입되었다. 파일을 head_chunks 라는 디렉토리를 만들어서 저장하고 이를 mmap을 이용해 로드한다. 실제 사용할때 로드하므로 초기 재시작시에 좀더 빠른 시작이 가능하다.

15~30% 정도 성능 향상과 15~50%의 메모리 절약이 가능하다고 하다. 다만, 어차피 쿼리를 통해 모든 파일을 로드해야하면 결국 메모리 사용량을 동일하다.(보통 모두 로드할일까지는 없으므로)

재시작시에는 mmap chunk부터 로드한다. 그러고나서 wal replay(로그 자체가 실제 scrape한 데이터가 흘러 들어오는거랑 동일하므로 그대로 다시 메모리로 재적재를 한다고 해서 리플레이라 한다)를 해서 mmap chunk 범위(tsdb이므로 time range)에 속하지 않으면 적재를 진행한다.

다만, chunks_head는 데이터 포맷상 디스크에만 저장하고, 실제 디스크에 저장되는 block chunk와 다르다. series에 대한 정보(심볼과 레이블 정보)가 없기 때문에 wal이 없으면 쓸모가 없다.

 

Prometheus TSDB 디렉토리 구조

 

즉, 위의 디렉토리에서 wal 디렉토리를 지우고 재시작하면, chunks_head 디렉토리도 초기화 된다.

이 head(memory) → mmap chunk로 로 변환되는 기준은 120개 sample이 모였거나 2시간이 지났을 때이다(120개 모이는건 금방이니 120개 기준으로 생성된다고 생각하면 된다).

 

Block chunks

chunkRange*3/2 시간이 지나면, 결국 Disk(persist)에 저장된다. block chunk 구조는 head chunk와 비슷하지만서도 다르다(series ref 등이 block chunk에는 없다. index 파일이 별도로 있다)

여기서 chunkRange는 조절할수 있고 디폴트 2h 이다(tsdb.min-block-duration 옵션이 바로 이것을 뜻한다). 그러니까 보통 3h이 지나면 chunkRange 에 해당되는 시간만큼의 데이터를 disk로 옮긴다는 얘기다.

이는 wal 파일이 날라 갔을 경우 최소 1시간~최대 3시간까지 데이터가 날라갈 수 있음을 뜻한다.

Q. tsdb.min-block-duration의 값을 줄이면 OOM 문제가 해결될까?

일단 이론적으로만 생각할때 그렇다. 하지만 2h가 기본값인 이유는 쿼리 성능향상 때문이다. 또한 너무 잦은 Disk작업을 유발할 수 있다. 만약 원격스토리지에 쿼리기능을 이관한다고 하면 의미가 있을 것으로 보인다.

 

WAL and Checkpoint

WAL은 128mb 로 파일이 계속 생성된다고 언급하였다. 파일을 append only로 만 사용하기 때문에 추가가 빠르다. 이 파일들은 메모리에 들고 있어야할 정보들을 의미하므로 Disk에 저장되는 순간 필요가 없어진다. 그 파일들을 하나의 디렉토리로 이동시키는 과정을 체크포인트라고 한다. 체크포인트후에 오래된 정보들을 head에서 정리한 후 파일 삭제를 진행한다.

 

Prometheus WAL 체크포인트 예시

체크포인트 순간: checkpoint.000003 디렉토리가 생기고 0,1번 파일들 옮겨진다. 그리고 나서 정리가 끝나면 이 디렉토리는 삭제된다. 이 과정을 하는 이유는 그냥 정리하면, 정리가 제대로 안된 상황에서 문제가 생기면 복구에 문제가 생기기 때문이라고 한다.

WAL Disk Format

데이터의 구분을 레코드라 표현하고, 총 5개의 레코드 타입이 존재한다. 

https://github.com/prometheus/prometheus/blob/main/tsdb/docs/format/wal.md

가장 기본이 되는 Series 레코드는 Label 정보를 담고 있고, 고유의 Series ID를 가지게 된다. Sample 레코드는 이 Series ID를 레퍼런스와 함께 t,v 값을 가지게 된다.

 

Preventing "Write Amplification"

Write Amplification을 막기 위해 메모리가 32kb가 채워지면 WAL을 기록한다고 한다.(이정도는 유실해도 된다는 의미인듯?)

*SSD에서(prometheus는 SSD용 이다!) page 단위로 저장되기 때문에 최소 page 단위 이하로 저장이 되면 쓸데없는 공간이 생긴다. 즉, 실제 기록하려는 데이터보다 물리적인 공간이 낭비된다는 의미

2.20버전 부터는 WAL compression이 기본으로 지원된다고 한다.

 

Blocks

Prometheus TSDB 디렉토리 구조(하위 전체)

 

prometheus 저장 디렉토리 구조는 다음과 같다.
chunks_head에 있는 000001 파일에는 여러개의 chunk가 있음을 기억하자.
persist로 저장되는 block은 01EM6Q~ 로 시작되는 디렉토리에 해당된다.

 

chunks

저장되는 chunks 들이다(이 역시 파일하나에 여러개의 chunk가 있다). 다만 chunks_head 와는 저장 포맷이 다르다. 
이는 chunks_head에 있는 chunk들의 인덱스 정보를 메모리가 들고 있는 반면, Disk에 저장된 chunks 에는 index 정보를 파일로 저장하고 있기 때문이다.


tomestones

실제 삭제하지 않고, 삭제표시만 한것을 말한다. prometheus api 중에 삭제 api가 존재한다. (/admin/tsdb/delete_series) 이는 나중에 병합 등이 발생하면 그 때 실제 삭제가 된다.

 

meta.json

Prometheus meta.json 예시

 

min/max 시간을 둬서 쿼리할때 이 시간대가 아니면 바로 이 block을 스킵할 수 있다. compaction은 시간이 지남에 따라 block이 병합되어 커진다고 했는데, 바로 병합 레벨에 따라 크기가 달라진다.(tsdb.max-block-duration으로 조정할수 있다)

 

compaction은 데이터 머지,압축,사용하지 않는 데이터 제거 등을 의미한다.


compaction을 하면 다음의 이점이 있다.

  • tomestone 삭제처리
  • 중복 데이터 머지(index 등)
  • (time) overlapping block fix(어떤 상황에서 겹치는 블락이 생기는지 모르겠으나, tsdb.allow-overlapping-blocks 옵션을 통해 compaction을 하면서 고쳐준다고 한다)
  • 여러 번의 block 로딩 및 deduplication 방지

index

Prometheus TSDB 인덱스 구조

 

위와 같은 구조를 가지고 있다.

 

Symbol Table: metric{container="xxx", pod="yyy"} 라고 하면, metric, container, xxx, pod, yyy 이 심볼
Label index는 inverted index 이다.
posting은 series ID를 말한다.