Coti Lab
Prometheus TSDB와 Gorilla 압축 비교
셀프호스팅

Prometheus TSDB vs Gorilla: 압축 알고리즘은 같은데 설계는 왜 갈라졌을까

Coti
9분

Prometheus 저장소의 tsdb/chunkenc/xor.go 맨 위에는 이 파일이 Damian Gryski의 go-tsz 프로젝트를 기반으로 작성됐다는 크레딧이 달려 있고, 파일 안쪽 주석에는 “Gorilla는 초 단위 해상도가 최대치지만 Prometheus는 밀리초 단위”라는 설명이 그대로 남아 있어요. 그런데 시간을 더 거슬러 올라가면 이야기가 좀 더 복잡해집니다. Prometheus 개발자 Björn Rabenstein은 2016년 공식 블로그에서 “Facebook이 인메모리 TSDB Gorilla 논문을 발표했을 때, 우리는 독립적으로 개발된 두 접근법 사이의 여러 유사점에 흥미를 느꼈다”고 직접 밝혔는데, 이 문장은 Prometheus가 Gorilla를 그대로 베낀 게 아니라 비슷한 문제를 각자 풀다가 논문에서 서로를 확인한 쪽에 가깝다는 걸 보여줘요. 그렇다면 Prometheus TSDB의 압축 설계는 Gorilla와 정확히 어디까지 같고 어디서부터 달라지는 걸까요?

Prometheus는 Gorilla 논문을 보기 전부터 비슷한 길을 걷고 있었다

Prometheus의 압축 설계는 Gorilla 논문을 참고해서 시작된 게 아니라, 독자적으로 개발하던 도중 논문과 겹치는 지점을 발견한 쪽에 가까워요. Rabenstein이 2016년 5월 8일 공식 블로그 글 “When (not) to use varbit chunks”에서 밝힌 내용에 따르면, Prometheus 1.x 시절 팀은 이미 자체적으로 압축 청크 포맷을 실험하고 있었고 Gorilla 논문이 나온 뒤에야 “독립적으로 개발된 두 접근법 사이의 여러 유사점”과 동시에 “연구할 가치가 있는 근본적인 차이점들”을 함께 발견했다고 설명합니다. 이때 만든 것이 Prometheus 1.x의 varbit 인코딩인데, 흥미롭게도 실측 압축률은 데이터 포인트당 약 1.28바이트로 Gorilla 논문이 보고한 1.37바이트보다 오히려 근소하게 더 좋았다고 같은 글에서 밝히고 있어요. 다만 이 varbit 인코딩에는 뚜렷한 대가가 있었는데, 더블 델타(double-delta) 인코딩된 청크는 인덱스로 임의 위치에 바로 접근할 수 있는 반면 varbit 청크는 오직 순차적으로만 읽을 수 있어서, 여러 시계열을 집계하며 특정 시점 샘플 하나씩만 골라 읽는 최악의 쿼리에서는 조회 시간이 수십 초까지 늘어질 수 있다고 같은 글이 경고하고 있습니다.

XOR 값 압축은 거의 그대로 가져오고, 타임스탬프 해상도만 조정했다

값 압축과 타임스탬프 압축의 세부 알고리즘 자체는 Gorilla TSDB 논문 리뷰델타의 델타 인코딩 글에서 이미 자세히 다뤘으니 여기서는 반복하지 않고, Prometheus가 이 알고리즘을 가져오면서 실제로 무엇을 바꿨는지에 집중할게요. 지금의 Prometheus 2.x TSDB(tsdb/chunkenc/xor.go)는 값에는 Gorilla와 사실상 동일한 XOR 인코딩을 쓰지만, 타임스탬프 쪽에서는 해상도 차이 때문에 조정이 필요했습니다. 소스코드 주석은 이 지점을 정확히 짚고 있는데, “Gorilla has a max resolution of seconds, Prometheus milliseconds. Thus we use higher value range steps with larger bit size”라고 명시하고 있어요. Gorilla 논문의 delta-of-delta 구간(예: [-63, 64], [-255, 256] 등)은 초 단위 스크레이프 간격을 기준으로 짜인 값이라, 기본 스크레이프 주기가 15초인데도 시간을 밀리초로 다루는 Prometheus에 그대로 적용하면 사소한 지터조차 훨씬 큰 정수로 잡혀 압축 효율이 떨어져요. 그래서 Prometheus는 같은 delta-of-delta 아이디어를 유지하면서 구간별 비트 크기를 밀리초 해상도에 맞게 새로 조정했습니다.

청크 120샘플, 블록 2시간 — 같은 실험에서 나온 같은 숫자

Prometheus가 청크 하나에 담는 샘플 수를 120개로 제한하는 이유는 Gorilla 논문이 실험으로 확인한 압축 최적점과 같은 맥락에 있어요. Prometheus 소스코드에는 DefaultSamplesPerChunk = 120이라는 상수가 명시돼 있고, Prometheus 공식 문서 역시 수집된 샘플을 2시간 단위 블록으로 묶어 저장한다고 밝히고 있습니다. 이 “2시간”이라는 숫자는 우연이 아니라, Gorilla 논문 리뷰 글에서 다뤘던 것처럼 Gorilla가 압축 블록을 2시간 이상으로 늘려도 데이터 포인트당 평균 압축 크기가 더 줄지 않는다는 실험 결과(논문 Figure 6)에서 가져온 절충점과 정확히 일치해요. 다시 말해 Prometheus는 Gorilla가 실험으로 찾아낸 “압축률과 조회 비용의 균형점”을 그대로 재사용하면서, 그 균형점을 자기 청크 구조에 맞게 120개라는 구체적인 샘플 수 제한으로 옮겨 담은 셈입니다.

순차 접근이냐 임의 접근이냐 — varbit이 결국 밀려난 이유

Prometheus가 압축률이 더 좋았던 varbit 인코딩 대신 지금의 Gorilla 스타일 XOR 청크를 기본값으로 택한 이유는 순수한 압축률이 아니라 조회 성능의 트레이드오프 때문이에요. 앞서 언급한 2016년 블로그 글은 varbit 청크의 단점을 “샘플에 순차적으로만 접근할 수 있는 반면, 더블 델타 인코딩된 청크는 인덱스로 임의 접근이 가능하다”고 명확히 정리하면서, 디스크 용량이나 디스크 I/O에 여유가 있다면 굳이 varbit을 쓰지 말고 더블 델타 인코딩을 유지하라고 권고했습니다. 이후 2017년경 이뤄진 Prometheus 2.x TSDB 전면 재설계에서는 압축률과 접근 성능 사이의 절충안으로, Gorilla가 채택한 것과 비슷한 구조의 XOR 청크(120샘플 단위로 잘려 메모리에 매핑되는 불변 청크)를 기본 인코딩으로 삼았어요. 압축률 하나만 극한까지 밀어붙이기보다, PromQL 쿼리가 특정 구간의 샘플을 얼마나 빠르게 훑어낼 수 있느냐를 함께 고려한 결정이라고 볼 수 있습니다.

Gorilla는 메모리 전용, Prometheus TSDB는 처음부터 디스크를 겨냥했다

두 시스템의 가장 근본적인 차이는 압축 알고리즘이 아니라 그 압축된 데이터를 어디에 어떻게 영속시키느냐에 있어요. Gorilla는 최근 26시간치 데이터를 압축된 형태 그대로 메모리에만 올려두고, 그보다 오래된 데이터는 별도의 기존 저장소로 넘기는 구조를 전제로 설계된 인메모리 TSDB입니다. 반면 Prometheus TSDB는 처음부터 디스크 영속을 기본 전제로 설계됐는데, 공식 문서에 따르면 수집된 샘플은 먼저 128MB 단위 세그먼트로 나뉜 WAL(Write-Ahead Log)에 기록되고, 이후 2시간 단위 블록으로 묶여 최대 512MB 세그먼트 파일에 저장됩니다. Gorilla가 특정 메트릭 키로 시계열 하나를 바로 찾아가는 단순한 키-값 접근 패턴을 전제로 했다면, Prometheus는 {job="node", instance="..."} 같은 라벨 조합으로 여러 시계열을 한 번에 묶어 조회해야 하는 PromQL을 지원해야 했기 때문에, 압축 청크와는 별도로 라벨 기반 역색인 구조까지 갖춰야 했다는 점도 두 시스템의 설계 목표가 처음부터 달랐다는 걸 보여줘요. Grafana + Prometheus 홈랩 모니터링에서 다룬 것처럼 실제 홈랩에서 Prometheus를 몇 달씩 돌려도 디스크가 생각보다 덜 차는 이유가 바로 이 조합, 즉 Gorilla에서 가져온 압축 알고리즘과 Prometheus가 독자적으로 얹은 디스크 영속·색인 계층이 함께 작동한 결과인 셈입니다.

그래서 홈랩 운영자에게 실질적으로 달라지는 건 무엇일까?

결론부터 말하면, 이 압축 설계 차이를 몰라도 Prometheus는 잘 동작하지만 알고 있으면 리소스 계획을 훨씬 정교하게 세울 수 있어요. 공식 문서가 밝힌 “평균 1~2바이트/샘플” 수치를 기준으로 삼으면, 지표 하나를 15초 주기로 30일 동안 수집했을 때 필요한 디스크 용량을 손쉽게 역산할 수 있고, 이는 --storage.tsdb.retention.time 옵션으로 보관 기간을 정할 때 훨씬 근거 있는 숫자를 쓸 수 있게 해줘요. 또 하나 기억해둘 만한 지점은, Loki + Grafana 로그 수집처럼 텍스트 로그를 다루는 스택은 Gorilla·Prometheus식 수치 압축과는 전혀 다른 방식(청크 단위 gzip/snappy 압축과 라벨 기반 인덱스)을 쓴다는 점이에요. 즉 “시계열 DB는 다 비슷하게 압축하겠지”라고 뭉뚱그리기보다, 다루는 데이터가 반복적인 수치인지 비정형 텍스트인지에 따라 압축 전략 자체가 완전히 갈린다는 걸 이해하고 있으면 홈랩 스택을 고를 때도 도움이 됩니다.

결론

Prometheus TSDB와 Gorilla는 값을 XOR로, 타임스탬프를 델타의 델타로 압축한다는 핵심 알고리즘은 거의 그대로 공유하지만, 그 알고리즘을 감싸는 설계는 서로 다른 목표를 좇아 갈라졌어요. Gorilla가 26시간치 메모리 상주와 단순 키 조회에 최적화됐다면, Prometheus는 WAL과 블록 압축, 라벨 역색인까지 갖춘 온디스크 영속 저장소로 스스로를 재설계했고, 그 과정에서 한때는 Gorilla보다 압축률이 더 좋았던 자체 varbit 인코딩을 순차 접근이라는 약점 때문에 포기하기도 했습니다. 처음 질문으로 돌아가면, Prometheus는 Gorilla의 압축 알고리즘을 거의 그대로 흡수하되 저장 구조와 조회 성능은 자신의 필요에 맞게 독자적으로 재설계했다는 게 두 시스템 비교의 핵심 결론이에요.

참고 자료

  • Rabenstein, B. “When (not) to use varbit chunks.” Prometheus 공식 블로그, 2016.5.8. 원문
  • “Storage.” Prometheus 공식 문서. 원문
  • Prometheus TSDB chunkenc/xor.go 소스. GitHub
  • Prometheus TSDB 청크 포맷 문서 tsdb/docs/format/chunks.md. GitHub
  • Pelkonen, T. et al. “Gorilla: A Fast, Scalable, In-Memory Time Series Database.” Proceedings of the VLDB Endowment, Vol. 8, No. 12, 2015. 원문 PDF

자주 묻는 질문

Q1. Prometheus는 Gorilla 논문을 그대로 베껴서 구현한 건가요?

아니요, 정확히는 아니에요. Prometheus 개발자 Björn Rabenstein이 2016년 공식 블로그에서 직접 밝힌 바에 따르면, Gorilla 논문이 나왔을 때 Prometheus 팀은 이미 독립적으로 비슷한 압축 방식을 실험하고 있었고, 논문을 읽은 뒤 "독립적으로 개발된 두 접근법 사이의 여러 유사점"을 발견하면서 서로 다른 부분도 함께 확인했다고 설명해요.

Q2. Prometheus 1.x의 varbit 인코딩과 지금 쓰는 XOR 청크는 같은 건가요?

다른 세대의 구현이에요. Prometheus 1.x는 자체 개발한 varbit 인코딩을 썼고 이때는 오히려 Gorilla가 보고한 1.37바이트보다 살짝 더 좋은 1.28바이트/샘플을 기록했지만, 순차 접근만 가능하다는 단점이 있었어요. 지금의 Prometheus 2.x TSDB는 `tsdb/chunkenc/xor.go`에서 Gorilla와 훨씬 가까운 구조의 XOR 청크를 쓰고 있어요.

Q3. Gorilla와 Prometheus의 실제 압축률은 얼마나 차이 나나요?

Gorilla 논문은 데이터 포인트 하나당 평균 1.37바이트를 보고했고, Prometheus 공식 문서는 "평균 1~2바이트"라고 밝히고 있어 같은 자릿수 범위예요. 다만 Prometheus 1.x의 varbit 인코딩은 같은 조건에서 약 1.28바이트로 Gorilla보다 근소하게 더 압축률이 좋았다고 공식 블로그가 직접 밝히고 있어요.

Q4. 타임스탬프 해상도가 초 단위와 밀리초 단위로 다른데 압축에 영향이 있나요?

네, 있어요. Prometheus 소스코드 `xor.go`의 주석은 "Gorilla는 초 단위 해상도가 최대치지만 Prometheus는 밀리초 단위라서, 더 큰 비트 크기의 값 범위 구간을 쓴다"고 명시하고 있어, delta-of-delta 구간 경계를 Prometheus가 자체적으로 조정했다는 걸 소스코드에서 직접 확인할 수 있어요.

Q5. 왜 청크 하나에 샘플을 120개까지만 담나요?

Prometheus 소스코드의 `DefaultSamplesPerChunk` 상수가 120으로 고정돼 있고, 이는 Gorilla 논문이 압축 블록을 2시간 이상으로 늘려도 압축률 개선이 거의 없다고 밝힌 실험 결과와 같은 맥락에서 나온 값이에요. Prometheus의 기본 블록 단위도 2시간이라, 두 시스템이 같은 실험적 최적점을 서로 다른 방식으로 채택한 셈이에요.

Q6. Gorilla와 Prometheus TSDB의 가장 큰 구조적 차이는 뭔가요?

메모리와 디스크 중 어디를 기본 저장소로 보느냐가 가장 큰 차이예요. Gorilla는 최근 26시간 데이터를 메모리에만 올려두는 것을 전제로 설계된 인메모리 TSDB인 반면, Prometheus TSDB는 처음부터 WAL과 블록 압축(compaction), 라벨 기반 역색인을 갖춘 온디스크 영속 저장소로 설계됐어요.

C
Coti 백엔드 개발자
프로필

댓글

첫 번째 댓글을 남겨보세요!