- Authors
- Name
- 개요
- Loki 아키텍처 심층 분석
- 배포 모드 선택
- LogQL 쿼리 마스터
- Grafana Alloy 기반 로그 수집 파이프라인
- 스토리지 설계와 리텐션 관리
- Loki vs Elasticsearch 비교
- 알럿 룰 구성
- 실전 트러블슈팅
- 장애 복구 절차
- Loki 3.x 주요 변경사항 요약
- 운영 체크리스트
- 마무리
- References

개요
로그는 시스템 장애 분석의 마지막 보루다. 메트릭이 "무엇이 문제인지"를 알려준다면, 로그는 "왜 문제가 발생했는지"를 설명한다. 그러나 대규모 분산 시스템에서 하루 수백 기가바이트의 로그를 수집하고 검색하는 일은 결코 단순하지 않다. Elasticsearch 기반의 ELK 스택은 강력한 풀텍스트 검색을 제공하지만, 인덱싱 비용과 운영 복잡도가 높다.
Grafana Loki는 이 문제에 대해 근본적으로 다른 접근을 취한다. 로그 본문을 인덱싱하지 않고, 레이블 기반의 인덱스만 유지한 채 로그 청크를 오브젝트 스토리지에 압축 저장한다. 이 설계 덕분에 Elasticsearch 대비 스토리지 비용이 70-80% 절감되며, 수 테라바이트 규모의 로그도 S3, GCS 같은 저렴한 오브젝트 스토리지에서 운영할 수 있다.
이 글에서는 Loki 3.x의 내부 아키텍처, 세 가지 배포 모드, LogQL 쿼리 문법과 최적화 패턴, Grafana Alloy를 활용한 로그 수집 파이프라인, TSDB 스토리지 설정과 리텐션, 알럿 룰 구성, 실전 트러블슈팅 사례, 그리고 장애 복구 절차까지 다룬다.
Loki 아키텍처 심층 분석
Loki는 마이크로서비스 기반 아키텍처로 설계되었으며, 각 컴포넌트가 명확한 역할을 담당한다. 핵심 철학은 "로그는 메트릭처럼 다룬다"이다. Prometheus의 레이블 모델을 로그에 그대로 적용하여, 동일한 레이블 셋으로 메트릭과 로그를 연계할 수 있다.
핵심 컴포넌트
Distributor는 로그 수집 에이전트(Alloy, Promtail 등)로부터 Push 요청을 수신하는 진입점이다. 수신된 로그 스트림의 레이블을 검증하고, 일관된 해시 링(Consistent Hash Ring)을 통해 적절한 Ingester로 라우팅한다. rate limiting과 validation을 이 단계에서 처리하므로, 잘못된 레이블이나 과도한 트래픽이 Ingester에 도달하기 전에 차단된다.
Ingester는 로그 데이터를 인메모리 버퍼에 축적한 뒤 청크 단위로 압축하여 오브젝트 스토리지에 플러시한다. WAL(Write-Ahead Log)을 활용하여 프로세스 비정상 종료 시 데이터 손실을 방지한다. Ingester가 가장 많은 메모리를 사용하는 컴포넌트이므로, 리소스 산정 시 가장 주의해야 한다.
Query Frontend는 클라이언트의 쿼리 요청을 수신하여 여러 Querier에 분산 실행한다. 시간 범위를 여러 구간으로 분할하는 query splitting과 결과 캐싱을 통해 쿼리 성능을 향상시킨다.
Querier는 실제 쿼리를 처리하는 워커다. Ingester의 인메모리 데이터와 오브젝트 스토리지의 블록 데이터를 동시에 탐색하여 결과를 병합한다.
Compactor는 오브젝트 스토리지의 인덱스 파일들을 주기적으로 병합하고, 리텐션 정책에 따라 만료된 데이터를 삭제한다. Loki 3.6부터는 수평 확장 가능한 Compactor가 도입되어 대규모 삭제 요청의 처리 속도가 크게 개선되었다.
Index Gateway는 인덱스 데이터에 대한 쿼리를 중앙에서 처리하여 Querier가 직접 오브젝트 스토리지에 접근하는 횟수를 줄인다. 마이크로서비스 모드에서 특히 유용하다.
데이터 흐름
[Application] --> [Grafana Alloy] --> [Distributor]
|
[Hash Ring]
|
[Ingester]
/ \
[WAL] [Object Storage (S3/GCS)]
|
[Compactor] <-------+
|
[Query Frontend] ---+---> [Querier]
|
[Index Gateway]
배포 모드 선택
Loki는 세 가지 배포 모드를 제공하며, 로그 볼륨과 운영 성숙도에 따라 선택한다.
Monolithic 모드
모든 컴포넌트가 단일 프로세스에서 실행된다. 개발 환경이나 하루 수 기가바이트 이하의 소규모 환경에 적합하다.
# Helm values - Monolithic 모드
loki:
deploymentMode: SingleBinary
auth_enabled: false
commonConfig:
replication_factor: 1
storage:
type: filesystem
schemaConfig:
configs:
- from: '2024-01-01'
store: tsdb
object_store: filesystem
schema: v13
index:
prefix: index_
period: 24h
singleBinary:
replicas: 1
persistence:
size: 50Gi
Simple Scalable 모드 (권장)
Loki Helm Chart의 기본 모드이며, read, write, backend 세 가지 타겟으로 분리된다. 하루 수백 기가바이트에서 약 1TB까지의 로그를 처리할 수 있어 대부분의 프로덕션 환경에서 최적의 선택이다.
# Helm values - Simple Scalable 모드
loki:
deploymentMode: SimpleScalable
auth_enabled: true
commonConfig:
replication_factor: 3
storage:
type: s3
s3:
endpoint: s3.ap-northeast-2.amazonaws.com
region: ap-northeast-2
bucketnames: company-loki-logs
access_key_id: ${AWS_ACCESS_KEY_ID}
secret_access_key: ${AWS_SECRET_ACCESS_KEY}
schemaConfig:
configs:
- from: '2024-01-01'
store: tsdb
object_store: s3
schema: v13
index:
prefix: index_
period: 24h
write:
replicas: 3
persistence:
size: 50Gi
resources:
requests:
cpu: '1'
memory: 2Gi
limits:
memory: 4Gi
read:
replicas: 3
resources:
requests:
cpu: '1'
memory: 2Gi
limits:
memory: 4Gi
backend:
replicas: 2
persistence:
size: 50Gi
gateway:
replicas: 2
write 타겟은 StatefulSet으로 배포되어 WAL 데이터를 유지하고, read 타겟은 Deployment로 배포되어 자동 스케일링이 가능하다. 반드시 리버스 프록시(gateway)를 앞단에 배치하여 API 요청을 read/write 노드로 라우팅해야 한다.
Microservices 모드
각 컴포넌트를 독립적인 프로세스로 배포한다. 하루 1TB를 초과하는 환경이나 컴포넌트별 세밀한 스케일링이 필요한 경우에 선택한다. 기본 Helm 배포 시 Distributor 3개, Ingester 3개, Querier 3개, Query Frontend 2개, Index Gateway 2개, Compactor 1개가 생성된다.
| 배포 모드 | 일일 로그 볼륨 | 운영 복잡도 | 주요 대상 |
|---|---|---|---|
| Monolithic | 수 GB 이하 | 낮음 | 개발/테스트 환경 |
| Simple Scalable | 수십 GB ~ 1TB | 중간 | 대부분의 프로덕션 |
| Microservices | 1TB 초과 | 높음 | 대규모 멀티 테넌트 |
LogQL 쿼리 마스터
LogQL은 Prometheus의 PromQL에서 영감을 받은 Loki 전용 쿼리 언어다. 로그 스트림 셀렉터로 시작하여 파이프라인 스테이지를 체이닝하는 구조이며, 로그 검색(Log Query)과 메트릭 변환(Metric Query) 두 가지 유형을 지원한다.
스트림 셀렉터와 라인 필터
모든 LogQL 쿼리는 스트림 셀렉터로 시작한다. 스트림 셀렉터는 Loki의 인덱스를 활용하므로 가능한 한 구체적으로 지정해야 쿼리 성능이 향상된다.
# 기본 스트림 셀렉터
{namespace="production", app="payment-service"}
# 라인 필터 체이닝 - 필터는 순서대로 적용되므로 가장 좁은 필터를 먼저 배치
{namespace="production", app="payment-service"}
|= "error"
!= "health-check"
|~ "timeout|connection refused"
# Loki 3.x의 패턴 매치 필터 - regex보다 10배 빠름
{namespace="production"} |> "error <_> timeout"
필터 연산자 정리:
|=: 문자열 포함 (contains)!=: 문자열 미포함 (not contains)|~: 정규식 매치!~: 정규식 미매치|>: 패턴 매치 (Loki 3.x)!>: 패턴 미매치 (Loki 3.x)
파서와 레이블 추출
Loki는 JSON, logfmt, pattern, regexp, unpack 파서를 제공한다. JSON이나 logfmt 형식이라면 전용 파서가 가장 효율적이며, 비정형 로그에는 pattern 파서가 regexp보다 빠르다.
# JSON 파서 - 구조화된 로그에서 필드 추출
{app="api-gateway"} | json | status >= 500
# logfmt 파서
{app="auth-service"} | logfmt | level="error" | duration > 5s
# pattern 파서 - nginx access log 파싱
{app="nginx"}
| pattern "<ip> - - [<timestamp>] \"<method> <path> <_>\" <status> <bytes>"
| status >= 400
| line_format "{{.ip}} {{.method}} {{.path}} {{.status}}"
# regexp 파서 - 복잡한 비정형 로그
{app="legacy-service"}
| regexp "(?P<timestamp>\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}) \\[(?P<level>\\w+)\\] (?P<message>.*)"
| level="ERROR"
파서 성능 비교 원칙: json/logfmt가 가장 빠르고, pattern이 그다음이며, regexp이 가장 느리다. 가능하면 line filter를 파서 앞에 배치하여 파싱할 로그 라인 수를 먼저 줄여야 한다.
메트릭 쿼리 - 로그를 숫자로 변환
LogQL의 메트릭 쿼리는 로그 스트림을 시계열 데이터로 변환한다. 이 기능은 별도의 메트릭 수집 없이 로그 데이터만으로 대시보드와 알럿을 구성할 수 있게 해준다.
# 초당 에러 로그 발생률
rate({namespace="production", app="payment-service"} |= "error" [5m])
# 서비스별 5xx 에러 카운트
sum by (app) (
count_over_time(
{namespace="production"} | json | status >= 500 [5m]
)
)
# unwrap을 활용한 응답 시간 P99 산출
quantile_over_time(0.99,
{app="api-gateway"}
| json
| unwrap response_time_ms
| __error__=""
[5m]
) by (endpoint)
# 평균 응답 시간 트렌드
avg_over_time(
{app="api-gateway"}
| json
| unwrap duration_seconds
| __error__=""
[5m]
) by (service)
# bytes_over_time - 로그 볼륨 모니터링
sum by (namespace) (bytes_over_time({namespace=~".+"} [1h]))
__error__="" 필터는 파싱이나 unwrap 단계에서 발생한 에러를 가진 라인을 제외하는 필수 패턴이다. 이것을 빠뜨리면 숫자가 아닌 값이 섞여 부정확한 결과가 나온다.
쿼리 최적화 실전 팁
- 스트림 셀렉터를 가능한 구체적으로 지정한다.
{app="payment"}대신{namespace="production", app="payment", env="prod"}처럼 레이블을 추가할수록 검색 대상 스트림이 줄어든다. - 라인 필터를 파서보다 먼저 배치한다.
|= "error" | json순서가| json |= "error"보다 빠르다. 라인 필터가 먼저 적용되어 파싱 대상 라인이 줄기 때문이다. - regex 대신 패턴 매치나 문자열 필터를 사용한다.
|~ "error.*timeout"대신|= "error" |= "timeout"이 훨씬 빠르다. - 시간 범위를 최소한으로 줄인다. 최근 1시간 검색과 최근 7일 검색의 성능 차이는 극적이다.
- 불필요한 레이블 추출을 하지 않는다.
| json은 모든 JSON 필드를 레이블로 추출하므로, 특정 필드만 필요하면| json status, duration처럼 명시한다.
Grafana Alloy 기반 로그 수집 파이프라인
Promtail은 2025년 2월 LTS에 진입했고, 2026년 3월 EOL에 도달한다. Grafana Alloy가 공식 후속 컬렉터이며, 로그뿐 아니라 메트릭, 트레이스, 프로파일링까지 단일 에이전트로 수집한다.
Promtail vs Alloy 비교
| 항목 | Promtail | Grafana Alloy |
|---|---|---|
| 수집 대상 | 로그만 | 로그, 메트릭, 트레이스, 프로파일 |
| 설정 언어 | YAML (scrape_configs) | River (HCL 유사 DSL) |
| OTel 호환 | 미지원 | 네이티브 OTLP 지원 |
| Kubernetes 로그 수집 | 파일 시스템 기반 (/var/log/containers) | Kubernetes API 기반 (loki.source.kubernetes) |
| 프로세싱 파이프라인 | stages 블록 | loki.process 컴포넌트 |
| 상태 | EOL (2026-03-02) | 적극 개발 중 |
| 마이그레이션 도구 | - | alloy convert --source-format=promtail |
Alloy 설정 예시 - Kubernetes 로그 수집
// Kubernetes Pod 디스커버리
discovery.kubernetes "pods" {
role = "pod"
}
// 레이블 재매핑 - 필요한 레이블만 선택
discovery.relabel "pods" {
targets = discovery.kubernetes.pods.targets
rule {
source_labels = ["__meta_kubernetes_namespace"]
target_label = "namespace"
}
rule {
source_labels = ["__meta_kubernetes_pod_name"]
target_label = "pod"
}
rule {
source_labels = ["__meta_kubernetes_pod_container_name"]
target_label = "container"
}
rule {
source_labels = ["__meta_kubernetes_pod_label_app"]
target_label = "app"
}
}
// Kubernetes 로그 소스
loki.source.kubernetes "pods" {
targets = discovery.relabel.pods.output
forward_to = [loki.process.pipeline.receiver]
}
// 로그 처리 파이프라인
loki.process "pipeline" {
// JSON 로그 파싱
stage.json {
expressions = {
level = "level",
message = "msg",
}
}
// level 레이블 정규화
stage.label_drop {
values = ["filename", "stream"]
}
// debug 로그 드롭 - 프로덕션에서 볼륨 절감
stage.match {
selector = "{level=\"debug\"}"
action = "drop"
}
forward_to = [loki.write.default.receiver]
}
// Loki 전송
loki.write "default" {
endpoint {
url = "http://loki-gateway.monitoring.svc:3100/loki/api/v1/push"
tenant_id = "production"
}
external_labels = {
cluster = "prod-kr-01",
region = "ap-northeast-2",
}
}
Promtail에서 Alloy로 마이그레이션
기존 Promtail 설정을 Alloy로 변환하는 명령어가 제공된다.
# Promtail 설정을 Alloy River 문법으로 변환
alloy convert --source-format=promtail --output=alloy-config.river promtail-config.yaml
# 변환 결과 검증 (dry-run)
alloy run --stability.level=generally-available alloy-config.river
# Kubernetes DaemonSet 배포 (Helm)
helm upgrade --install alloy grafana/alloy \
--namespace monitoring \
--set alloy.configMap=true \
-f alloy-values.yaml
스토리지 설계와 리텐션 관리
TSDB 인덱스 스토어
Loki 2.8부터 도입된 TSDB는 현재 권장 인덱스 스토어다. 기존 BoltDB Shipper 대비 쿼리 성능이 개선되었고, TCO(Total Cost of Ownership)가 낮다. 인덱스 기간은 반드시 24시간으로 설정해야 한다.
# loki.yaml - TSDB + S3 스토리지 설정
schema_config:
configs:
- from: '2024-01-01'
store: tsdb
object_store: s3
schema: v13
index:
prefix: loki_index_
period: 24h
storage_config:
tsdb_shipper:
active_index_directory: /loki/tsdb-index
cache_location: /loki/tsdb-cache
aws:
s3: s3://ap-northeast-2/company-loki-chunks
s3forcepathstyle: false
# Loki 3.4+ 통합 스토리지 설정 (Thanos Object Storage Client)
common:
storage:
s3:
endpoint: s3.ap-northeast-2.amazonaws.com
region: ap-northeast-2
bucketnames: company-loki-data
access_key_id: ${AWS_ACCESS_KEY_ID}
secret_access_key: ${AWS_SECRET_ACCESS_KEY}
Loki 3.4부터 Thanos Object Storage Client가 통합되어, Mimir, Pyroscope 등 다른 Grafana 데이터베이스와 동일한 스토리지 설정 형식을 사용한다. 기존 AWS SDK 기반 설정도 호환되지만, 신규 배포에서는 통합 설정을 사용하는 것이 권장된다.
리텐션 정책 구성
리텐션은 Compactor가 담당한다. retention_enabled: true를 반드시 설정해야 하며, 글로벌 리텐션과 스트림 단위 리텐션을 모두 지원한다.
# loki.yaml - 리텐션 설정
compactor:
working_directory: /loki/compactor
compaction_interval: 10m
retention_enabled: true
retention_delete_delay: 2h
retention_delete_worker_count: 150
limits_config:
retention_period: 720h # 글로벌: 30일
retention_stream:
- selector: '{namespace="production"}'
priority: 1
period: 2160h # 프로덕션: 90일
- selector: '{namespace="staging"}'
priority: 2
period: 168h # 스테이징: 7일
- selector: '{level="debug"}'
priority: 3
period: 72h # 디버그 로그: 3일
주의사항: retention_delete_delay는 삭제 지연 시간이다. 실수로 리텐션을 너무 짧게 설정한 경우, 이 시간 내에 설정을 되돌리면 데이터 손실을 방지할 수 있다. 최소 2시간 이상으로 설정할 것을 강력히 권장한다.
스토리지 비용 최적화
- 청크 압축 알고리즘:
snappy(기본값)보다gzip이 압축률이 높지만 CPU 사용량이 증가한다. 스토리지 비용이 주요 관심사라면 gzip을, 쓰기 성능이 중요하면 snappy를 선택한다. - chunk_target_size: 기본 1.5MB다. 이 값을 높이면 오브젝트 수가 줄어 API 호출 비용이 감소하지만, Ingester 메모리 사용량이 증가한다.
- S3 Intelligent-Tiering: 로그 액세스 패턴은 시간이 지날수록 감소하므로, S3 Intelligent-Tiering이나 수명 주기 정책을 적용하면 장기 보관 비용을 절감할 수 있다.
Loki vs Elasticsearch 비교
로그 관리 솔루션 선택 시 가장 많이 비교되는 두 시스템의 차이점을 정리한다.
| 비교 항목 | Grafana Loki | Elasticsearch (ELK) |
|---|---|---|
| 인덱싱 방식 | 레이블만 인덱싱 | 풀텍스트 인덱싱 |
| 스토리지 비용 | 매우 낮음 (오브젝트 스토리지) | 높음 (SSD 블록 스토리지) |
| 쿼리 언어 | LogQL | KQL / Lucene |
| 검색 속도 (비정형) | 느림 (grep 방식) | 빠름 (역인덱스) |
| 검색 속도 (레이블 기반) | 빠름 | 빠름 |
| 메모리 요구량 | 낮음 | 높음 (JVM 힙) |
| 운영 복잡도 | 중간 | 높음 (샤딩, 리밸런싱) |
| Grafana 연동 | 네이티브 | 플러그인 필요 |
| 일일 100GB 기준 비용 | Elasticsearch 대비 20-30% | 기준 |
| 적합한 환경 | Kubernetes, 클라우드 네이티브 | 풀텍스트 검색, 보안 분석(SIEM) |
Loki를 선택해야 하는 경우: Kubernetes 환경에서 비용 효율적인 로그 관리가 필요하고, Grafana를 메인 대시보드로 사용하며, 레이블 기반의 구조화된 쿼리가 주요 패턴인 경우.
Elasticsearch를 유지해야 하는 경우: 비정형 로그에 대한 임의의 풀텍스트 검색이 빈번하거나, 보안 로그 분석(SIEM), 비즈니스 인텔리전스 용도로 로그를 활용하는 경우.
알럿 룰 구성
Loki는 LogQL 기반의 알럿 룰을 지원하며, Ruler 컴포넌트가 주기적으로 쿼리를 실행하여 조건이 충족되면 Alertmanager로 알럿을 전송한다.
# loki-alert-rules.yaml
groups:
- name: application-errors
rules:
- alert: HighErrorRate
expr: |
sum by (app, namespace) (
rate({namespace="production"} |= "error" [5m])
) > 10
for: 5m
labels:
severity: critical
annotations:
summary: 'High error rate in {{ $labels.app }}'
description: '{{ $labels.app }} in {{ $labels.namespace }} has error rate {{ $value }}/s for 5 minutes.'
- alert: SlowResponseTime
expr: |
quantile_over_time(0.95,
{app="api-gateway"}
| json
| unwrap response_time_ms
| __error__=""
[5m]
) by (app) > 3000
for: 10m
labels:
severity: warning
annotations:
summary: 'P95 response time exceeds 3s for {{ $labels.app }}'
- alert: LogVolumeSpike
expr: |
sum by (namespace) (bytes_over_time({namespace=~".+"} [5m]))
/ sum by (namespace) (bytes_over_time({namespace=~".+"} [5m] offset 1h))
> 3
for: 15m
labels:
severity: warning
annotations:
summary: 'Log volume spike in {{ $labels.namespace }} (3x increase)'
LogVolumeSpike 알럿은 특히 유용하다. 로그 볼륨이 급증하면 어플리케이션에 문제가 있거나, 잘못된 디버그 설정이 배포된 것일 수 있다. 방치하면 Ingester OOM이나 스토리지 비용 폭증으로 이어진다.
실전 트러블슈팅
증상 1: Ingester OOM (Out of Memory)
원인: 레이블 카디널리티가 너무 높거나, 단일 테넌트의 활성 스트림 수가 과도한 경우. 대표적으로 request_id, trace_id 같은 고유 값을 레이블로 설정한 경우에 발생한다.
진단:
# 활성 스트림 수 확인
curl -s http://loki:3100/metrics | grep loki_ingester_streams_created_total
# 테넌트별 스트림 수 확인
curl -s http://loki:3100/loki/api/v1/index/stats \
--header "X-Scope-OrgID: production" \
--data-urlencode 'query={namespace="production"}' \
--data-urlencode 'start=1h'
# Ingester 메모리 사용량 확인
kubectl top pods -n monitoring -l app.kubernetes.io/component=ingester
해결:
- 고카디널리티 레이블을 구조화된 메타데이터(structured metadata)로 전환한다.
max_streams_per_user리미트를 적절히 설정한다.- Ingester 메모리 리밋을 늘리거나 replicas를 증가시킨다.
증상 2: 쿼리 타임아웃
원인: 너무 넓은 시간 범위 쿼리이거나, 스트림 셀렉터가 너무 포괄적인 경우.
해결:
# limits_config 튜닝
limits_config:
max_query_length: 721h # 최대 쿼리 시간 범위
max_query_parallelism: 32 # 쿼리 병렬도
query_timeout: 5m # 단일 쿼리 타임아웃
split_queries_by_interval: 30m # 쿼리 분할 간격
max_query_series: 500 # 최대 시리즈 수
증상 3: out-of-order 로그 거부
Loki는 기본적으로 로그가 타임스탬프 순서대로 도착해야 한다. 여러 에이전트가 동일 스트림에 쓰거나, 네트워크 지연이 있는 경우 순서가 뒤바뀔 수 있다.
# Loki 3.4+ out-of-order 허용 설정
limits_config:
unordered_writes: true
max_chunk_age: 2h
증상 4: Compactor 지연
Compactor가 처리 속도를 따라가지 못하면 인덱스 파일이 쌓이고 쿼리 성능이 저하된다.
해결: Loki 3.6의 수평 확장 가능한 Compactor를 활용하거나, compaction_interval을 줄이고 Compactor의 CPU/메모리 리소스를 늘린다.
장애 복구 절차
Ingester 장애 복구
# 1. 장애 Ingester Pod 확인
kubectl get pods -n monitoring -l app.kubernetes.io/component=ingester
# 2. WAL 상태 확인
kubectl exec -n monitoring ingester-0 -- ls -la /loki/wal/
# 3. 장애 Pod 재시작 (WAL이 있으므로 데이터 복구 가능)
kubectl delete pod -n monitoring ingester-0
# 4. 복구 후 해시 링 상태 확인
curl -s http://loki:3100/ring | jq '.shards[] | {addr, state, tokens}'
# 5. flush 강제 실행 (필요시)
curl -X POST http://loki:3100/ingester/flush
오브젝트 스토리지 접근 장애
오브젝트 스토리지에 접근할 수 없으면 Ingester의 WAL에 데이터가 계속 쌓이다가 결국 OOM이 발생한다.
- 즉시 S3/GCS 연결 상태와 IAM 권한을 확인한다.
- Ingester의
flush_check_period와chunk_idle_period를 일시적으로 늘려 시간을 벌 수 있다. - 스토리지가 복구되면
POST /ingester/flush로 축적된 데이터를 플러시한다. - 장기 장애 시 Ingester 디스크 용량을 모니터링하고, 필요하면 PVC를 확장한다.
Loki 3.x 주요 변경사항 요약
- 3.0: Bloom filter 기반 쿼리 가속(실험적), 네이티브 OpenTelemetry 지원, 패턴 매치 필터 연산자 도입
- 3.3: 쿼리 성능 개선, Structured Metadata 지원 강화
- 3.4: Thanos Object Storage Client 통합, 사이징 가이던스 제공, Promtail이 Alloy에 병합 발표
- 3.6: 수평 확장 Compactor, OpenTracing에서 OpenTelemetry tracing 라이브러리로 리팩토링
Bloom filter는 특정 텍스트 문자열(에러 메시지, UUID 등)을 검색하는 필터 쿼리의 속도를 크게 향상시킨다. 아직 실험적 기능이지만, 대규모 환경에서 grep 방식의 풀스캔을 획기적으로 줄일 수 있다.
운영 체크리스트
프로덕션 Loki 배포 전 반드시 확인해야 할 항목을 정리한다.
배포 전
- 일일 로그 볼륨을 추정하여 배포 모드를 결정했는가
- TSDB 인덱스 스토어를 사용하고, period는 24h인가
- 오브젝트 스토리지 버킷과 IAM 권한을 생성했는가
- replication_factor를 3 이상으로 설정했는가 (프로덕션)
- Ingester의 메모리 리밋을 충분히 할당했는가 (최소 4Gi 권장)
- WAL 디렉토리의 PVC가 충분한 크기인가
레이블 설계
- 레이블 카디널리티가 관리 가능한 수준인가 (스트림 수만 개 이내)
- request_id, trace_id, user_id 같은 고유 값을 레이블로 사용하지 않는가
- namespace, app, env 등 고정 레이블만 사용하고 있는가
- Structured Metadata를 활용하여 검색 가능한 메타데이터를 분리했는가
운영 중
- Compactor의 retention_enabled가 true인가
- 리텐션 정책이 네임스페이스/환경별로 차등 적용되는가
- Ingester 메모리 사용량과 활성 스트림 수를 모니터링하는가
- 로그 볼륨 급증에 대한 알럿이 설정되어 있는가
- Alloy에서 debug 로그 드롭 등 볼륨 제어 파이프라인이 있는가
- 정기적으로 LogQL 쿼리 성능을 리뷰하는가
마무리
Grafana Loki는 "인덱스 없이도 대규모 로그를 효율적으로 관리할 수 있다"는 것을 증명한 시스템이다. Elasticsearch의 풀텍스트 인덱싱이 필요 없는 대부분의 운영 환경에서, Loki는 70-80% 적은 비용으로 동일한 수준의 장애 분석 역량을 제공한다.
핵심은 레이블 설계다. 과도한 레이블은 Ingester OOM과 성능 저하를 야기하고, 너무 적은 레이블은 모든 쿼리를 풀스캔으로 만든다. namespace, app, env, level 수준의 레이블로 시작하여, 실제 쿼리 패턴에 따라 점진적으로 조정하는 것이 권장된다.
Loki 3.x의 Bloom filter, 패턴 매치 필터, 네이티브 OTel 지원은 기존의 "검색 속도가 느리다"는 약점을 빠르게 보완하고 있다. Promtail에서 Alloy로의 전환과 TSDB 인덱스 스토어 도입은 이제 선택이 아닌 필수다. Simple Scalable 모드로 시작하여, 로그 볼륨이 증가하면 Microservices 모드로 전환하는 점진적 접근이 실패를 줄이는 가장 현실적인 전략이다.
References
- Grafana Loki Architecture - 공식 문서
- Loki Deployment Modes - 공식 문서
- LogQL Query Reference - 공식 문서
- Loki Storage and Retention - 공식 문서
- Grafana Loki 3.4 Release - Standardized Storage and Promtail Merging into Alloy
- Migrate from Promtail to Grafana Alloy - 공식 문서
- Single Store TSDB - 공식 문서
- Loki v3.6 Release Notes
- Loki Metric Queries - 공식 문서