Loki Recording Rules로 LogQL 비용 줄이고 Grafana Alerting 노이즈를 구조적으로 없애는 법
로그 기반 알림을 처음 구성하고 나서 한동안은 꽤 뿌듯했습니다. Loki에서 에러 로그를 카운트하는 쿼리를 만들고, 임계값을 넘으면 Slack으로 알림이 가는 구조가 돌아가고 있었으니까요. 그런데 얼마 지나지 않아 두 가지 문제가 생겼습니다. 대시보드를 열 때마다 수십 GB짜리 로그를 매번 집계하느라 응답이 수십 초씩 걸렸고, 알림은 순간적인 스파이크에도 발화해서 새벽에 온-콜 호출이 와도 "또 노이즈겠지"하고 무시하는 습관이 생겼습니다. 알림 피로(alert fatigue)가 쌓이면 진짜 장애 알림도 놓치게 됩니다. 실제로 그랬습니다. 어느 날 밤 DB 연결 오류가 났는데, 알림이 너무 자주 왔던 터라 몇 번이나 씹다가 결국 대응이 늦었습니다.
Loki Recording Rules는 반복 실행 비용이 높은 LogQL 쿼리를 미리 계산해 메트릭으로 저장하고, Grafana Alerting의 Pending Period와 Recovering 상태와 결합하면 노이즈 알림을 구조적으로 제거할 수 있습니다. 이 글을 읽고 나면 "어떤 알림 설정이 노이즈를 만드는가"와 "어떻게 바꾸면 온-콜 호출이 줄어드는가"에 대한 실용적인 답을 가져갈 수 있습니다. Recording Rule의 원리부터 SLO 알림까지, 현업에서 바로 가져다 쓸 수 있는 설정 예시를 중심으로 풀어봅니다.
핵심 개념
Recording Rule이 해결하는 문제
Grafana Loki는 로그 스트림을 대상으로 LogQL을 실행합니다. 문제는 같은 집계 쿼리가 대시보드 패널 5개, 알림 규칙 3개에서 동시에 실행되면 원본 로그를 7번 스캔한다는 점입니다. 쿼리 수에 비례해 중복 비용이 누적되고, 로그 볼륨이 클수록 이 누적 비용은 빠르게 커집니다.
Recording Rule은 Loki의 Ruler 컴포넌트가 설정된 주기마다 LogQL 표현식을 한 번만 평가하고, 그 결과를 Prometheus 호환 시계열로 저장하는 방식으로 이 문제를 해결합니다.
로그 스트림(Loki) → Ruler(LogQL 평가, 1회) → Remote Write → Mimir/Prometheus
↓
Grafana Alerting (메트릭 기반 알림)Ruler: Loki 아키텍처에서 alerting/recording rule을 주기적으로 평가하는 컴포넌트입니다. 마이크로서비스 모드에서는 독립 프로세스로, 모놀리식 모드에서는 내장 기능으로 동작합니다.
Rule의 두 가지 유형
Loki Rule에는 두 종류가 있고, 실제로는 이 둘을 같은 그룹 안에서 조합해서 씁니다.
| 유형 | 역할 | 결과물 |
|---|---|---|
| Recording Rule | 복잡한 LogQL 집계를 주기적으로 평가해 저장 | Prometheus 시계열 메트릭 |
| Alerting Rule | 조건(LogQL 또는 메트릭 쿼리)을 평가해 임계값 초과 시 알림 발생 | Firing 알림 이벤트 |
저도 처음엔 알림 규칙만 쓰면 된다고 생각했는데, 실제로 운영해보면 "비싼 LogQL → Recording Rule로 메트릭화 → 그 메트릭 기반으로 Alerting Rule 작성"이 훨씬 안정적입니다.
기본 설정 구조
핵심 YAML 구조는 이렇습니다. Recording Rule과 Alerting Rule이 같은 그룹 안에 순서대로 들어갑니다.
groups:
- name: http_error_rates
interval: 1m # 이 그룹의 평가 주기
rules:
# Step 1: 비싼 LogQL을 메트릭으로 사전 계산
- record: job:http_errors:rate5m
expr: |
sum by (job, status_code) (
rate({job="api-server"} | json | status_code >= 400 [5m])
)
# Step 2: 사전 계산된 메트릭을 참조해 알림 설정
- alert: HighErrorRate
expr: job:http_errors:rate5m > 0.05
for: 5m
labels:
severity: critical
annotations:
summary: "Error rate {{ $value | humanize }} on {{ $labels.job }}"record 필드의 네이밍 컨벤션(job:metric_name:aggregation)은 Prometheus 커뮤니티 표준입니다. 나중에 메트릭이 많아질수록 이 규칙을 지켜두면 관리가 훨씬 편해집니다.
Ruler 컴포넌트 설정
Loki 설정 파일에서 Ruler를 활성화하고 Remote Write 대상을 지정합니다.
ruler:
enable_api: true
evaluation_interval: 1m
storage:
type: local
local:
directory: /loki/rules
remote_write:
enabled: true
client:
url: http://mimir:9009/api/v1/pushRemote Write: Prometheus 생태계의 표준 프로토콜로, 메트릭을 외부 저장소로 전송합니다. Mimir, Thanos, VictoriaMetrics 모두 이 프로토콜을 지원합니다.
WAL(Write-Ahead Log): Ruler가 평가 결과를 Remote Write로 전송하기 전에 로컬에 임시 저장하는 로그입니다. Ruler가 재시작되어도 미전송 데이터를 복구할 수 있어 PersistentVolume 연결이 필요합니다.
알림 노이즈를 줄이는 최신 기능 (2025~2026)
Recording Rule과 함께 반드시 알아둬야 할 Grafana Alerting의 변화가 두 가지 있습니다. 실전 예시에서 쓰이는 설정들과 직접 연결되니 미리 파악해두면 도움이 됩니다.
Recovering 상태 (2025년 5월): 알림 조건이 해소된 직후 바로 Normal로 전환되지 않고, keep_firing_for에 설정한 시간 동안 Recovering 상태를 유지합니다. flapping(알림이 켜졌다 꺼졌다 반복되는 현상)을 억제하는 데 효과적입니다.
NoData/Error Pending Period (2026년 2월): 이전에는 데이터가 없거나 평가 오류가 발생하면 즉시 알림이 나갔습니다. 이제는 이 상태에도 Pending Period가 적용되어, 일시적인 데이터 공백이 오탐으로 이어지는 상황이 방지됩니다.
실전 적용
예시 1: API 에러율 알림 — 노이즈 제거 전후 비교
실무에서 가장 많이 보이는 패턴입니다. 처음에 이렇게 설정하는 경우가 많습니다.
# 노이즈가 많은 설정
- alert: ApiErrors
expr: count_over_time({job="api"} |= "ERROR" [1m]) > 5
# for 없음 → 1분 창에서 에러 6개만 발생해도 즉시 발화for 파라미터가 없으면 평가 주기마다 조건이 참이 되는 순간 알림이 발화합니다. 배포 직후 잠깐 올라가는 에러, 외부 의존성의 일시적 타임아웃 같은 것들이 전부 알림으로 들어옵니다.
# 개선된 설정
groups:
- name: api_slo
interval: 1m
rules:
# Step 1: 에러율을 비율로 계산 (절대 카운트가 아닌 비율로)
- record: job:api_error_rate:rate5m
expr: |
sum(rate({job="api"} | logfmt | level="error" [5m]))
/
sum(rate({job="api"} | logfmt [5m]))
# Step 2: 비율 기반 알림 + Pending Period + Recovering
- alert: ApiHighErrorRate
expr: job:api_error_rate:rate5m > 0.01
for: 10m
keep_firing_for: 5m
labels:
severity: warning
annotations:
summary: "API error rate {{ $value | humanizePercentage }} (job={{ $labels.job }})"위 예시는 로그 포맷이
logfmt(level=error msg="..."형태)인 경우를 기준으로 합니다. 실제 로그 형식에 맞게logfmt대신json파서나|= "ERROR"라인 필터로 교체해서 사용하면 됩니다.
| 설정 | 역할 |
|---|---|
rate() 기반 비율 |
트래픽 변동에 관계없이 에러 비율이 일정하게 유지됨 |
for: 10m |
10분간 지속적으로 1% 초과할 때만 발화 (순간 스파이크 무시) |
keep_firing_for: 5m |
조건 해소 후 5분간 Recovering 유지, flapping 방지 |
예시 2: AWS ALB 액세스 로그 → 메트릭 파이프라인
별도 Prometheus exporter 없이 ALB 액세스 로그에서 직접 메트릭을 뽑는 패턴입니다. 솔직히 처음에는 "이게 되나?" 싶었는데, 실제로 잘 됩니다.
groups:
- name: alb_metrics
interval: 1m
rules:
- record: alb:request_count:rate5m
expr: |
sum by (target_group, status_code) (
rate({job="alb-access-logs"}
| regexp `(?P<status_code>\d{3}) (?P<target_group>[^ ]+)` [5m])
)
- record: alb:error_rate:rate5m
expr: |
sum by (target_group) (
rate({job="alb-access-logs"}
| regexp `(?P<status_code>[45]\d{2})` [5m])
)
/
sum by (target_group) (
rate({job="alb-access-logs"} [5m])
)
- alert: AlbHighErrorRate
expr: alb:error_rate:rate5m > 0.05
for: 5m
labels:
severity: critical주의: ALB 액세스 로그는 공백으로 구분된 20개 이상의 필드로 이루어져 있어, 위 regexp 패턴이 실제 로그 형식에 따라 원하는 필드를 정확히 추출하지 못할 수 있습니다. 복사해서 바로 쓰기보다 Grafana Explore에서 실제 ALB 로그 샘플을 파싱해보고 필드 추출이 올바른지 확인한 뒤 사용하는 것을 권장합니다.
regexp 파이프라인: LogQL에서 정규식으로 로그 라인의 일부를 레이블로 추출합니다. 복잡한 정규식은 Ruler 평가 비용을 높이므로 가능하면
json이나logfmt파서를 우선 고려하는 것이 좋습니다.
예시 3: Multi-Window Multi-Burn-Rate SLO 알림
다음은 SRE 팀이 적용하는 수준의 SLO 알림입니다. 처음 도입이라면 예시 1, 2를 먼저 안정화한 뒤 참고하시는 것을 권장합니다.
99.9% 가용성 SLO(월간 허용 에러 예산: 약 43.8분)를 로그 기반으로 구현하는 패턴입니다. 단순 임계값보다 훨씬 정교하게 "지금 이 에러율이 얼마나 빠르게 에러 예산을 소진하고 있는가"를 측정합니다.
burn rate 배수가 처음엔 낯설게 느껴질 수 있습니다. 직관적으로 이해하면, 14.4x는 이 속도로 에러가 계속 발생하면 월간 에러 예산을 약 2일 만에 소진한다는 뜻이고, 6x는 약 5일 만에 소진한다는 뜻입니다. 빠른 burn rate는 단기 창에서 즉시 감지하고, 느리지만 지속되는 burn rate는 장기 창으로 잡아내는 구조입니다.
groups:
- name: api_slo_burn_rate
interval: 1m
rules:
# 다양한 시간 창의 에러율 메트릭 사전 계산
- record: job:api_error_rate:rate5m
expr: |
sum(rate({job="api"} | logfmt | level="error" [5m]))
/ sum(rate({job="api"} | logfmt [5m]))
- record: job:api_error_rate:rate1h
expr: |
sum(rate({job="api"} | logfmt | level="error" [1h]))
/ sum(rate({job="api"} | logfmt [1h]))
- record: job:api_error_rate:rate6h
expr: |
sum(rate({job="api"} | logfmt | level="error" [6h]))
/ sum(rate({job="api"} | logfmt [6h]))
# Fast-burn: 5분 + 1시간 창 동시 초과 → 즉각 대응 필요
- alert: SLO_FastBurn
expr: |
job:api_error_rate:rate5m > (14.4 * 0.001)
and
job:api_error_rate:rate1h > (14.4 * 0.001)
for: 2m
labels:
severity: critical
slo: api_availability
# Slow-burn: 6시간 창 초과 → 에러 예산이 서서히 줄어드는 중
- alert: SLO_SlowBurn
expr: job:api_error_rate:rate6h > (6 * 0.001)
for: 1h
labels:
severity: warning
slo: api_availability| 알림 | 창 | 배수 | 의미 |
|---|---|---|---|
SLO_FastBurn |
5분 + 1시간 | 14.4x | 이 속도면 월간 에러 예산을 약 2일 만에 소진 (즉시 대응 필요) |
SLO_SlowBurn |
6시간 | 6x | 이 속도면 월간 에러 예산을 약 5일 만에 소진 (방치하면 위험) |
두 창을 and로 연결하는 이유는 단기 스파이크 오탐을 막기 위해서입니다. 5분 창만 사용하면 일시적 트래픽 급등에 발화하고, 1시간 창만 사용하면 빠른 장애 감지가 늦어집니다.
장단점 분석
장점
| 항목 | 내용 |
|---|---|
| 쿼리 비용 절감 | 동일 LogQL이 대시보드·알림 여러 곳에서 참조될 때 로그 스캔을 1회로 줄임 |
| 대시보드 응답 속도 | 사전 계산된 시계열을 읽으므로 수십 GB 로그 집계가 밀리초 단위 응답 |
| 노이즈 억제 | for + keep_firing_for 조합으로 일시적 스파이크와 flapping 알림을 구조적으로 차단 |
| SLO 구현 | 로그 데이터만으로 Prometheus SLO 생태계(burn rate, error budget)를 구성 가능 |
| 클라우드 비용 절감 | CloudWatch 등 외부 메트릭 서비스 의존도 감소 (Qonto 사례) |
| UI 관리 | Grafana UI에서 직접 Recording Rule 생성·관리 가능 (Terraform/YAML 없이도 운영 가능) |
단점 및 주의사항
| 항목 | 내용 | 대응 방안 |
|---|---|---|
| Ruler 인프라 추가 | 별도 Ruler 컴포넌트 운영 필요. 처음 도입 때 Ruler StatefulSet 설정이 생각보다 손이 많이 갑니다. | StatefulSet + PV(WAL) 구성 권장; Helm grafana/loki 차트로 관리 |
| 소급 계산 불가 | 규칙 활성화 이전 구간 메트릭은 존재하지 않음 | 규칙 배포 후 충분한 워밍업 시간 확보 후 대시보드 공개 |
| 레이블 카디널리티 | 고카디널리티 레이블(request ID, user ID 등) 포함 시 메트릭 저장 비용 폭증. user_id를 레이블로 넣었다가 Mimir가 OOM으로 죽는 것을 한 번 직접 겪고 나서야 배웠습니다. |
sum by()에서 반드시 필요한 레이블만 선별 |
| 평가 주기 설계 | 짧은 주기는 Ruler 부하 증가, 긴 주기는 알림 지연 | 중요도별로 그룹을 분리해 주기를 다르게 설정 |
| LogQL 복잡성 | unwrap, json, regexp 중첩 시 Ruler 평가 비용 증가 |
레이블 셀렉터와 라인 필터로 스트림을 먼저 좁힌 후 파서 적용 |
레이블 카디널리티(Label Cardinality): 메트릭의 레이블 값이 가질 수 있는 고유한 경우의 수입니다.
user_id처럼 값이 무한히 늘어나는 레이블을 메트릭에 포함하면 시계열 수가 폭발적으로 증가하고 저장소와 쿼리 비용이 함께 올라갑니다.
실무에서 가장 흔한 실수
-
for파라미터 생략: 알림 규칙을 처음 작성할 때for를 빠뜨리는 경우가 많습니다. 기본값이 0이라 조건이 참이 되는 즉시 발화하고, 순간 스파이크가 전부 알림으로 변환됩니다. 최소for: 5m부터 시작하는 것을 권장합니다. -
절대 카운트로 임계값 설정:
count_over_time(...) > 100처럼 절대값으로 설정하면 트래픽이 늘어났을 때 정상 상황에서도 알림이 발화합니다. 에러 비율(rate(error) / rate(total))로 전환하면 트래픽 변동에 무관하게 안정적인 알림이 됩니다. -
고카디널리티 레이블을 Recording Rule에 포함:
by (user_id, request_id)같은 집계를 Recording Rule에 넣으면 메트릭 시계열 수가 사용자 수 × 요청 수만큼 폭증합니다. Recording Rule은 집계 후 의미 있는 차원(job,service,status_code수준)만 남기는 용도로 사용하는 것이 적합합니다.
마치며
이 설정을 적용하고 나서 가장 먼저 달라진 건 새벽 온-콜 호출 빈도였습니다. for: 10m과 keep_firing_for: 5m을 붙이고 나서 순간 스파이크성 알림이 사라지자, 오히려 알림이 울렸을 때 "이건 진짜 확인하러 가야 하는구나"라는 신뢰감이 생겼습니다. 알림 품질이 올라가면 팀의 대응 속도도 따라 올라갑니다.
지금 바로 시작해볼 수 있는 3단계:
-
현재 Loki 알림 규칙에서
for파라미터를 확인해봅니다.for가 없거나for: 0인 규칙이 있다면 최소for: 5m으로 변경하는 것만으로도 노이즈가 크게 줄어듭니다.lokitool rules validate <rules.yaml>명령어로 YAML 문법을 미리 검증할 수 있습니다. -
가장 자주 쿼리되는 LogQL 표현식을 Recording Rule로 옮겨봅니다. Grafana Explore에서 느린 쿼리를 찾아
record:블록으로 변환하고, 대시보드 패널을 사전 계산된 메트릭 참조로 교체하는 방식으로 시작할 수 있습니다. -
단순 임계값 알림 하나를 Multi-Window Burn Rate 알림으로 교체해봅니다. 위 예시 3의 YAML을 복사해
job레이블만 자신의 서비스명으로 바꾸는 것만으로 기본 SLO 알림 구조를 체험해볼 수 있습니다. 단,logfmt파서가 실제 로그 형식과 맞는지 Grafana Explore에서 먼저 확인하는 과정을 거치는 것을 권장합니다.
참고 자료
- Alerting and recording rules | Grafana Loki documentation
- Manage recording rules | Grafana Loki documentation
- Create recording rules | Grafana documentation
- Alert rule evaluation | Grafana documentation
- Pending period added to alert states | Grafana Labs
- Grafana-managed alert rule "Recovering" state | Grafana Labs
- When SLOs reduce alert noise | Grafana documentation
- How to implement multi-window, multi-burn-rate alerts with Grafana Cloud | Grafana Labs
- Grafana Loki: performance optimization with Recording Rules, caching, and parallel queries
- Grafana Loki: LogQL and Recording Rules for metrics from AWS Load Balancer logs (ITNEXT)
- Building a cost-efficient network observability platform using Grafana Loki | Qonto (Medium)
- Grafana Alerting best practices
- Grafana Loki: Optimising log based metrics — DEV Community