Skip to content

알림

Ongrid 의 알림 서브시스템은 모든 활성 규칙 행을 워크하고 적절한 백엔드 (메트릭 + 트레이스 spanmetrics 용 Prom, 로그용 Loki) 에 predicate 가 매칭되는지 묻고 incidents 테이블에 발화를 기록하는 단일 틱 루프 입니다.

별도의 Alertmanager 없음, 별도의 rules 파일 없음. 규칙은 MySQL 에 살고, evaluator 는 30 초 캐시 새로고침으로 폴링하며, 알림은 채널 레지스트리를 통해 팬아웃합니다.

14 규칙 kind

규칙은 kind 컬럼으로 저장. 컴파일러가 그것으로 디스패치.

컴파일러는 rules.go, evaluator 는 evaluators_phaseA.go + evaluators_phaseB.go 에 있습니다.

8+6 분할은 HLD-004 의 Phase-A (메트릭) / Phase-B (로그 + 트레이스), 2026-05-08 안착.

메트릭 kind (Phase A)

Kind동작Spec 필드
metric_rawPromQL 표현식이 predicate. 반환된 벡터 엔트리당 발화.expr
metric_anomaly롤링 기준선 윈도우 위의 Z-score 또는 MAD.metric, method, baseline_window, baseline_step, deviation, for_seconds
metric_forecastpredict_linear(metric[fit_window], predict_seconds) <op> threshold.metric, fit_window, predict_seconds, operator, threshold
metric_burn_rateSLO 위의 Google SRE 다중 윈도우 다중 burn. 모든 윈도우가 트리거해야 함.sli, slo, burns[].window, burns[].multiplier

레거시 prom_query kind 는 metric_raw 로 이름 변경. 레거시 metric_threshold 폼은 이제 저장 시 metric_raw 로 컴파일되는 UI 전용 엔트리 — 별도 evaluator 없음.

go
// internal/manager/biz/alert/rules.go:36
type MetricRawRule struct {
    ID         uint64
    RuleKey    string
    Name       string
    Severity   string
    ScopeType  string // host / global / monitoring_pipeline
    RunbookURL string
    Labels     map[string]string
    Expr       string // canonical predicate, e.g. `up == 0`
}

로그 + 트레이스 kind (Phase B)

Kind동작Backend
log_matchLoki 에 대해 count_over_time(<stream> |~ <filter> [window]) <op> threshold. 레이블 셋별 발화.Loki
log_volumelog_match 와 같은 형태, 현재 윈도우 카운트 vs 절대 임계값.Loki
trace_latencyhistogram_quantile(q, sum by(le)(rate(traces_spanmetrics_latency_bucket[w]))) > threshold_ms.Prom (spanmetrics)
trace_error_rate100 * (sum rate(traces_spanmetrics_calls_total{status_code="STATUS_CODE_ERROR"}) / sum rate(...)) > pct.Prom (spanmetrics)

트레이스 kind 는 Prometheus 를 쿼리 하지 Tempo 가 아닙니다. Spanmetrics generator 가 Tempo 를 scrape 하고 traces_spanmetrics_* 시리즈를 다시 Prom 에 씁니다 — Prom 쿼리는 알림 evaluator 를 하나의 쿼리 엔진에 두고 모든 연산자 필터링 / 임계값 로직을 재사용합니다.

Scope 타입

모든 규칙은 scope_type ∈ {host, global, monitoring_pipeline} 를 가짐. Kind 별 기본은 rules.godefaultScopeForKind 에 정의.

  • host — incident 가 device_id 를 운반해야 함. evaluator 가 Prom 결과 레이블에서 device_id 레이블을 파싱; validateFiring 이 없는 호스트 범위 발화를 거부.
  • global — 단일 호스트에 고정되지 않은 서비스 레벨 알림 (trace_, log_).
  • monitoring_pipeline — Ongrid 자체에 대한 메타 알림 (scrape_down, prom_ingest_fail, ...).

Evaluator 틱

PipelineEvaluator.evaluateInterval 마다 (기본 5 분, PipelineEvaluatorOpts.Interval 로 구성 가능) 실행.

go
func (e *PipelineEvaluator) evaluate(ctx context.Context) {
    now := e.now()
    if e.edges != nil {
        e.refreshDeviceStalenessGauge(ctx, now)
    }
    if e.prom != nil {
        e.evaluatePromQuery(ctx, now)
        e.evaluateMetricAnomaly(ctx, now)
        e.evaluateMetricForecast(ctx, now)
        e.evaluateMetricBurnRate(ctx, now)
        e.evaluateTraceLatency(ctx, now)
        e.evaluateTraceErrorRate(ctx, now)
    }
    if e.logq != nil {
        e.evaluateLogMatch(ctx, now)
        e.evaluateLogVolume(ctx, now)
    }
}

nil 백엔드는 해당 kind 를 조용히 건너뜀 — Loki 다운이 메트릭 알림을 깨지 않음.

Dedup + 회복

evaluator 가 틱 전반에 걸쳐 firingSnapshot[ruleKey] = set<dedupeKey> 를 추적. 지난 틱에 있었고 이번 틱에 없는 키 → PromQL 의 비교 필터가 시리즈를 떨어뜨림 → predicate 해제 → "prom condition cleared" 와 함께 SystemResolveIncident 발화. 이것이 별도 "resolve" evaluator 없이 알람이 회복되는 방법.

Dedupe 키 형태: pipeline:<rule_key>:<sorted-label-set> — provenance 레이블 (__name__, ongrid_source) 은 제거되어 같은 알람이 임베디드와 클라우드 컬렉터 모두에서 보고되어도 두 incident 가 아닌 하나로 dedupe (labelSetKey).

채널 팬아웃

Incident 발화 시 Notifier.MaybeNotify 경로가 ChannelResolver 를 참고:

  1. 규칙별 고정 — rule.notify_channel_ids_json 이 비어 있지 않으면 그 채널 id 만 매칭 (그리고 활성화된 것만).
  2. 그 외에는 모든 활성 notification_channels 행이 match_severity_minmatch_scope_types 로 필터링.
  3. 아무것도 매칭되지 않으면 resolver 는 DefaultChannels 에서 시드된 합성 채널 리스트로 폴백하여 알림이 사라지지 않게 함.

router.go 참고.

억제

두 내장 억제 규칙 (inhibit.go), 시끄러운 기본 케이스 커버:

  • edge_offline:edge_X 가 모든 host:X:* 를 억제 — edge 가 도달 불가 시 그 위의 모든 호스트 범위 알람이 억제.
  • pipeline:prom_ingest_failpipeline:scrape_down:* 를 억제 — Prometheus 자체가 인제스트할 수 없을 때 모든 "target down" 알람은 노이즈.

미래의 inhibition_rules 테이블이 이를 관리자 정의 그룹으로 확장.

쿨다운 + 댐핑

NotifyOpts.Cooldown (기본 10 분) 이 같은 dedupe_key 에 대한 재알림을 제한. 댐핑 필터는 Usecase.MaybeNotify 내부에 위치하여 채널 resolver 와 inhibitor 는 매 발화에서 여전히 실행 — 실제 Notifier.Send 만 건너뜀.

같이 보기

  • RCA — incident 발화 시 일어나는 일.
  • 로그 — Loki 와 log_match / log_volume evaluator.
  • 트레이스 — Tempo 와 trace_latency / trace_error_rate evaluator.
  • 채널 개요 — Slack / Telegram / Lark / DingTalk / WeCom + webhook 채널 구성 방법.
  • 알림 규칙 스키마 — 규칙 행의 와이어 포맷.