grep

DevOps

Observability를 위한 LGTM 첫걸음

Ellie여기어때

2025년 12월 22일

원문에서 보기 ↗

안녕하세요.

여기어때컴퍼니 SRE팀에서 클라우드 업무를 담당하고 있는 엘리입니다.

여기어때 서비스 전반의 모니터링을 통합적으로 관리하기 위해 Observability 스택 LGTM을 25년에 도입했습니다. 처음 도입할 때는 Grafana를 제외하면 나머지 구성 요소를 직접 다뤄본 경험이 없었습니다. 그래서 각 구성 요소의 배포 모드, 아키텍처 구조, 컴포넌트별 역할을 이해하는 것이 큰 과제였습니다.

특히 Mimir, Tempo, Loki뿐만 아니라 EKS 환경에서 Helm Chart를 활용한 배포도 처음이었기 때문에 초반에는 시행착오가 많았습니다.

이 글은 저와 같이 LGTM을 처음 접하는 분들께, 각 구성 요소의 구조를 이해하는 데 도움이 되고자 작성했습니다.

1. LGTM이란?

LGTM은 Loki, Grafana, Tempo, Mimir의 앞글자를 딴 것으로 log, metric, trace를 하나로 통합 Grafana Labs의 Observability 스택입니다.

서버나 애플리케이션 규모가 커지면 “왜 느려졌지?”, “어디서 실패가 났지?” 같은 질문에 답하기가 점점 어려워지는데, 이런 상황에서 LGTM은 세 가지 신호를 함께 분석해 문제의 원인과 영향을 빠르게 파악할 수 있게 도와줍니다.

Loki

라벨 기반 인덱싱으로 log를 비용 효율적으로 저장하고 검색합니다.

대규모 환경에서도 특정 서비스나 네임스페이스의 오류 log를 쉽고 빠르게 좁혀가며 원인 단서를 찾기에 적합합니다.

Grafana

log, metric, trace를 하나의 인터페이스에서 탐색하고 시각화할 수 있습니다. 대시보드와 Explore, Alert, Oncall workflow 연계로 분석에서 대응까지 이어집니다.

Tempo

분산 trace 백엔드로, 요청의 실제 경로와 구간별 지연을 추적합니다. traceID 중심의 설계 덕분에 병목 지점을 정확하게 찾아내 원인 파악에 용이합니다.

Mimir

Prometheus와 완전히 호환되는 수평 확장형 metric 저장소입니다.

지표를 장기간 보관하고, PromQL을 사용해 빠르게 분석할 수 있어 대시보드와 알림 기능의 기반이 됩니다.

2. 배포 모드

Grafana Mimir, Tempo, Loki는 배포 모드에 따라 구성과 운영 방식이 달라집니다. 운영 환경의 규모와 목적에 따라 배포 방식을 선택하는 것은 단순히 리소스 크기를 결정하는 문제가 아닙니다. 시스템의 확장성과 안정성, 그리고 운영의 복잡성 사이에서 가장 알맞은 균형을 찾는 중요한 결정이기도 합니다.

Monolithic Mode

출처 : https://grafana.com/docs/mimir/latest/references/architecture/deployment-modes/

Read-Write Mode

해당 모드는 Grafana Mimir 3.0 릴리스 버전에서 공식으로 제거되었습니다.

“Removal of experimental features: Some deprecated and experimental flags, including the read-write deployment mode, have been removed.”

출처 : https://grafana.com/docs/mimir/latest/references/architecture/deployment-modes/

Scalable Monolithic Mode

Microservice

출처 : https://grafana.com/docs/mimir/latest/references/architecture/deployment-modes/

저희는 LGTM 스택을 Microservice 구조로 구축했지만, 이 글에서는 각 구성 요소를 읽기와 쓰기 흐름으로 나누어 설명드리는 편이 더 이해하기 쉬울 것 같습니다.

그래서 본격적으로 컴포넌트별 설명에 들어가기 전에, 아래의 기본 아키텍처 다이어그램을 통해 데이터가 어떻게 수집되고 조회되는지 큰 흐름을 먼저 살펴보겠습니다.

이 구조를 미리 이해하면, 뒤에 이어질 각 컴포넌트별 아키텍처 설명이 훨씬 쉽게 다가올 것입니다.

읽기 작업

쓰기 작업

3. Mimir 아키텍쳐

구조 및 설명

Distributor

Distributor는 Mimir의 쓰기 경로 시작점으로, 시계열 데이터를 수집하고 유효성을 검증한 뒤 여러 Ingester로 분산 전송합니다.

(1) 유효성 검사

(2) 데이터 복제 및 분산

(3) Rate Limit 관리

*아래에는 개념 설명과는 별도로, 초기에 알았으면 구현과 운영이 더 수월했을 옵션들을 간단히 메모로 묶어보았습니다.

(1) 해당 시리즈의 최신 타임스탬프보다 이전인 샘플(순서가 잘못된 샘플)은 err-mimir-sample-out-of-order 에러 log가 발생하며 -ingester.out-of-order-time-windowout_of_order_time_window를 통해 최신 샘플 이전의 샘플을 허용할 수 있음

Ingester

Distributor에서 전달받은 데이터를 메모리(WAL 버퍼)에 임시 저장하고, 주기적으로 장기 저장소에 업로드합니다.

(1) WAL(Write-Ahead Log)

(2) 데이터 업로드

(3) Replication / HA 구조

(1) Ingester가 장기저장소에 보내는 주기가 있으므로, 데이터 누락없이 스케일인을 하기 위해 flush-blocks-on-shutdown 옵션을 활성화 해야 함.replication factor가 적용되는 컴포넌트의 replicas 수를 결정할 때는 replication factor의 수도 고려해야 함.

  • loki, tempo는 Ingester 하위에 해당 옵션이 존재하지만 mimir는 Ingester가 아닌 blocks-storage하위인 -blocks-storage.tsdb.flush-blocks-on-shutdown에 있음.

(2) Ingester 리소스를 설정할 때는 replication factor 뿐만이 아니라 -ingester.max-global-series-per-usermax-global-series-per-user옵션, Querier가 Ingester를 조회하면서 발생하는 부하 등도 고려해야 함.

Compactor

장기 저장소에 업로드된 여러 블록을 병합하고, 중복 데이터 제거 및 retention 정책을 관리합니다.

(1) Block Compaction

(2) Retention / Deletion 관리

(1) Compactor는 block_ranges단위로 compaction을 수행하는데 큰 시간의 블록으로 compaction을 할 때는 리소스를 많이 사용하는데, 이때 ebs의 처리량도 급증하기 때문에 필요 시 storageclass, vac를 통해 처리량 증설

(2) storage class의 경우 volumeClaimTemplates는 Immutable이기 때문에 vac를 추천하며, vac는 EKS 1.31부터 지원

Query Frontend

클라이언트의 쿼리 요청을 큐잉하고, 캐싱 및 쿼리 분할(splitting)을 수행합니다. Querier가 안정적으로 처리할 수 있도록 쿼리 부하를 조정합니다.

(1) Queuing

큐잉 메커니즘

(2) Splitting

(3) Caching

(4) Query Sharding

(1) 쿼리 샤딩 기능인 parallelize_shardable_queries의 default가 false이므로 사용하기 위해 true로 변경해줘야 함.

(2) 캐싱 기능 옵션인 cache_results도 default가 false이므로 사용하기 위해 true로 변경하고, results_cache.backend를 지정해줘야 함.

Query Scheduler

Query Frontend로부터 전달된 쿼리 요청을 중앙 큐에서 관리하고, 여러 Querier로 효율적으로 분배합니다.

즉, 쿼리 부하를 균형 있게 분산하는 중앙 스케줄러입니다.

(1) 중앙 큐 관리

(2) 쿼리 분배

(1) grafana에 문의 시 Query Scheduler는 리소스 사용량이 매우 낮은 컴포넌트이므로 수평확장이 필요하지 않기 때문에 HPA 설정이 적용되어있지 않다는 답변을 받음

(출처: https://github.com/grafana/mimir/issues/7368#issuecomment-2772366031)

(2) scheduler가 동시에 처리할 수 있는 최대 요청 수 옵션인 -query-scheduler.max-outstanding-requests-per-tenantmax_outstanding_requests_per_tenant를 설정할 때는 Querier에 리소스도 고려해야 함

Querier

실제 쿼리 실행을 담당하는 워커입니다. Query Scheduler 또는 Frontend 큐에서 쿼리를 가져와 실행합니다.

(1) 데이터 수집

(2) 쿼리 실행

(1) 대시보드에서 조회 시 Client.Timeout exceeded while awaiting headers 에러가 발생하는 경우 timeout을 늘려 해결하려고 하는 경우 Querier의 timeout을 늘려도 datasource timeout에 걸리면 Query Frontend에서 쿼리를 cancel 하기 때문에 -querier.timeout(defualt: 2m) 를 늘릴 때, datasource의 default가 30s이므로 같이 조정해야 함.

Store-Gateway

장기 저장소(Object Storage)에서 블록 데이터를 읽고, Querier의 쿼리에 필요한 청크와 메타데이터를 제공합니다.

(1) 주기적 동기화

(2) block index header

(3) 캐싱을 통한 성능 최적화

(1) mimir Store Gateway의 캐싱 기능을 사용할 경우-blocks-storage.bucket-store.index-cache.backend, -blocks-storage.bucket-store.chunks-cache.backend, -blocks-storage.bucket-store.metadata-cache.backend옵션의 backenddefault가 index-cache의 기본값이 inmemory이고, chunks-cache, metadata-cache는 ““ 이므로 유의.

(2) replication factor 관련

  • replication factor가 적용되는 컴포넌트의 replicas 수를 결정할 때는 replication factor의 수도 고려해야 함.

  • replication factor가 너무 낮으면 데이터 유실 가능성이 있으며, 너무 높게 잡으면 메모리를 많이 사용.

(3) HPA/KEDA 및 Helm Chart 관련

  • HelmChart에서 hpa/keda를 제공하지 않는 컴포넌트가 있기 때문에 extraObjects를 통해 추가해 줘야 함.

  • extraObjects를 통해 오토스케일링을 추가하면 Ingester replicas와 hpa의 replicas가 충돌 날 수 있기 때문에 Ingester spec.replicas를 ignoreDifferences처리해 줘야 함.

(4) 링 컴포넌트 shutdown 관련

-링을 사용하는 컴포넌트의 경우 unregister_on_shutdown활성화 추천.

-파드가 종료되었을 때 링에서 파드가 빠지지 않는 경우 unhealthy가 되고, unhealthy가 유지되면 too many unhealthy instances in the ring 에러 발생.

4. Tempo 아키텍쳐

tempo의 구조는 mimir와 매우 유사하지만, Store Gateway가 없고, metric-generator가 존재합니다.

또한 mimir는 metric을 저장/조회하였지만, tempo는 trace를 저장/조회할 수 있습니다.

아래에서는 Mimir와 공통된 구성 요소는 생략하고, Tempo에서 달라진 부분 위주로 설명하겠습니다.

구조 및 설명

Metric Generator

5. Loki 아키텍쳐

Loki의 전체 구조는 Grafana Mimir와 유사하지만, Store Gateway 대신 Index Gateway가 존재하며, log 데이터를 저장·조회하는 데 최적화되어 있습니다.

아래에서는 Mimir와 공통된 구성 요소는 생략하고, Loki에서 달라진 부분 위주로 설명하겠습니다.

구조 및 설명

Index Gateway

6. 마무리

이번 글에서는 LGTM 스택의 주요 구성 요소인 Mimir, Tempo, Loki의 구조와 특징을 정리했습니다. 각 시스템은 공통된 분산 아키텍처를 기반으로 하지만, 저장하는 데이터의 형태에 따라 설계 방향이 조금씩 다르다는 것을 알 수 있었습니다. 처음 LGTM을 도입했을 때는 모든 구성 요소가 낯설었지만, 구조를 하나씩 이해하고 직접 배포해 보며 Observability 스택의 동작 원리를 깊이 배울 수 있었습니다.

LGTM 스택이 실제 서비스 환경에 완전히 안착하기까지는 아직 갈 길이 남아 있지만, 이 과정을 통해 단순한 설정 이상의 운영 감각과 문제 해결 경험을 얻을 수 있었습니다.

앞으로도 LGTM 스택을 꾸준히 학습하고 개선하며, 보다 안정적인 Observability 환경을 만들어가고자 합니다.

이 글이 LGTM을 처음 접하는 분들께 구조를 이해하고 자신만의 Observability 스택을 설계하는데 작은 출발점이 되길 바랍니다.

읽어주셔서 감사합니다.