본문 바로가기

대시보드와 경보 설계

RED·USE·업무 메트릭으로 계층형 대시보드를 만들고 SLI·SLO·오류 예산에 맞는 대응 가능한 경보를 설계합니다.

메트릭을 많이 수집하는 것과 장애를 빨리 발견하는 것은 다른 일입니다. 대시보드가 JVM 차트 수십 개를 나열하고, 경보가 CPU 사용률 80% 같은 지표에만 반응하면, 정작 사용자가 실패하는 순간을 놓칠 수 있습니다.

그래서 먼저 요청의 성공, 지연 시간, 트래픽 같은 서비스의 증상을 보고, 리소스와 의존성으로 범위를 좁힌 뒤, 로그와 트레이스에서 개별 원인을 확인하는 계층을 만듭니다. 이 글에서는 첫 대시보드 화면의 구성, SLI와 SLO, 오류 예산과 소진율 경보, 대응할 수 있는 경보의 조건을 차례로 다룹니다.

SLO · CALCULATION AND ACTION

오류 예산 소진율을 대응 조건과 함께 판단한다

첫 화면은 사용자 증상·RED·업무 대기·수집 최신성을 보여 줍니다. 증상에서 영역·리소스·의존성·트레이스/로그로 좁히고 SLO 계약으로 경보를 판단합니다.

두 창을 함께 검사하는 원 severity 분기원 BurnRateDecision.severity는 첫 조건에서 short와long 모두14.4이상이면 PAGE, 그렇지 않고 short6이상·long3이상이면 TICKET, 나머지는 NO_ALERT를 반환한다. 그림은 이 순수 함수의 분기이며 최소 트래픽·최신성·알림 전달은 별도 조건이다. 같은 5노드4관계를 모바일에서도 유지하고 오류 예산 계산 지원 패널은 보존한다.짧은·긴 창 모두 ≥14.4?PAGE_FAST_BURN짧은 ≥6이고긴 창 ≥3?TICKET_SLOW_BURNNO_ALERT예아니요예아니요
두 창을 함께 검사하는 원 severity 분기원 BurnRateDecision.severity는 첫 조건에서 short와long 모두14.4이상이면 PAGE, 그렇지 않고 short6이상·long3이상이면 TICKET, 나머지는 NO_ALERT를 반환한다. 그림은 이 순수 함수의 분기이며 최소 트래픽·최신성·알림 전달은 별도 조건이다. 같은 5노드4관계를 모바일에서도 유지하고 오류 예산 계산 지원 패널은 보존한다.짧은·긴 창모두 ≥14.4?PAGE_FAST_BURN짧은 ≥6이고긴 창 ≥3?TICKETSLOW_BURNNO_ALERT예아니요예아니요

SLI와 오류 예산 · 계산 근거

  1. 유효 이벤트와 정상 판정

    SLI = 정상/유효 이벤트입니다. 검증 4xx·타임아웃·취소를 분모/실패에 어떻게 넣는지 계약으로 정합니다.

  2. 목표에서 허용 오류율

    SLO 99.9%라면 허용 오류율은 0.1%입니다. 관측 구간의 오류율을 이 예산 비율과 비교합니다.

  3. 소진율 예시

    오류율 1% ÷ 허용 0.1% = 10배 소진입니다. 이것만으로 월 예산의 10%를 썼다는 뜻은 아닙니다. 구간 길이와 이벤트/트래픽을 함께 봅니다.

운영 규칙 · 알림을 보내는 조건

  1. 짧은 창과 긴 창

    지속 시간과 소진 임계값을 목표·긴급도에서 계산합니다. 빠른 장애와 느린 예산 소비 규칙을 분리합니다.

  2. 최소 이벤트와 최신성

    저트래픽 비율의 잡음, 수집 공백, 규칙 평가 실패를 분리합니다. 본문 BurnRateDecision은 최소 개수나 알림 전달을 구현하지 않습니다.

  3. Page 또는 Ticket

    지속 사용자 영향과 즉시 대응이 필요한 경우 호출합니다. 담당자·runbook·시간/범위·대시보드를 제공하고 동일 원인 경보를 묶습니다.

데이터 없음·규칙 실패·알림 전달 실패는 정상0과 별도로 감시합니다. 배포 변화의 상관관계를 원인으로 단정하지 않습니다. 과거 트래픽 재생과 회고로 창·최소 이벤트·대응 절차를 검증합니다.

그림의 위쪽은 이 글의 BurnRateDecision.severity가 따르는 분기입니다. 짧은 창과 긴 창의 소진율이 모두 14.4 이상이면 PAGE_FAST_BURN을, 그렇지 않고 짧은 창이 6 이상이면서 긴 창이 3 이상이면 TICKET_SLOW_BURN을, 나머지는 NO_ALERT를 돌려줍니다. 최소 트래픽, 데이터 최신성, 알림 전달은 이 순수 함수 밖의 별도 조건입니다. 아래 칸들은 SLI와 오류 예산을 계산하는 방법과, 실제로 알림을 보낼 때 확인할 운영 규칙을 정리합니다.


첫 대시보드 화면

RED는 초당 요청 수(Rate), 오류(Errors), 지속 시간(Duration)을 서비스의 관점에서 봅니다. USE는 사용률(Utilization), 포화도(Saturation), 오류(Errors)를 CPU, 메모리, 스레드 풀, 연결 풀 같은 자원에 적용합니다. 회원 게시판에는 여기에 허용·거부 비율, 가져오기 대기량과 그 경과 시간 같은 업무 신호를 더합니다.

첫 화면은 증상, 트래픽, 포화도, 배포 버전, 수집 최신성을 몇 개의 패널로만 보여 줍니다. 평균 지연 시간만 두지 말고 p50, p95, p99나 SLO 버킷을 함께 봅니다. 평균 100 ms는 99%가 10 ms이고 1%가 9초인 상태를 숨길 수 있습니다. 요청이 거의 없을 때 비율이 크게 흔들리는 것도 절대 개수와 함께 해석합니다.

src/main/java/board/monitoring/ServiceSnapshot.java
package board.monitoring;

import java.time.Duration;

public record ServiceSnapshot(
        double requestsPerSecond,
        double errorRatio,
        Duration p95,
        int pendingEntries,
        Duration oldestPendingAge,
        boolean scrapeCurrent
) {
    public String headline() {
        if (!scrapeCurrent) return "OBSERVABILITY_STALE";
        if (errorRatio > 0.02) return "USER_ERRORS_HIGH";
        if (oldestPendingAge.compareTo(Duration.ofMinutes(5)) > 0) {
            return "BACKLOG_AGING";
        }
        return "WITHIN_OBJECTIVE";
    }
}

이 판정은 실제 경보 엔진을 대신하지 않지만, 첫 화면이 어떤 질문에 답해야 하는지를 코드로 적어 둡니다. headline()은 scrapeCurrent, errorRatio, oldestPendingAge 세 값만 분기에 씁니다. requestsPerSecond, p95, pendingEntries를 담고 있어도 그 기준은 아직 검사하지 않으므로, WITHIN_OBJECTIVE를 모든 성능·업무 목표를 통과했다는 뜻으로 읽지 말고 세 조건에서 앞선 경고가 없다는 이름으로 해석합니다.


대시보드를 따라 내려가는 흐름

서비스 개요에서 엔드포인트, 영역, 결과별 세부 화면으로, 그다음 인스턴스의 리소스와 의존성으로, 마지막으로 예시값(exemplar), 트레이스, 로그로 내려갑니다. 패널의 링크가 같은 시간 범위와 레이블 필터를 넘겨주면, 장애 중에 쿼리를 다시 입력하지 않아도 됩니다.

대시보드 변수에 사용자 ID나 값의 범위가 제한되지 않은 경로를 넣지 않습니다. 인스턴스 변수가 이미 사라진 파드까지 포함하는지, 배포 변수가 현재 배포 버전을 반영하는지 검증합니다. 대시보드 JSON과 경보 규칙은 버전 관리하고, UI에서 고친 내용도 검토할 수 있는 변경 사항으로 내보냅니다.


SLI와 SLO

가용성 SLI(service level indicator)는 정상 이벤트 수를 유효 이벤트 수로 나눈 값으로 정의합니다. 클라이언트가 잘못 보낸 검증 실패의 4xx를 서비스 실패에 넣을지, 타임아웃과 취소를 어떻게 분류할지는 제품의 규칙으로 정합니다. 지연 시간 SLI는 임계값 안에 완료된 요청의 비율로 만들 수 있습니다. 메트릭 이름보다, 무엇을 집계 대상 이벤트로 보고 무엇을 정상으로 판정하는지가 먼저입니다.

월 99.9%의 SLO(service level objective)는 약 0.1%의 오류 예산(error budget)을 허용합니다. 모든 순간에 오류 0을 요구하지 않고, 예산을 얼마나 빨리 쓰는지로 위험을 봅니다. 계획된 유지보수와 트래픽이 적은 구간을 어떻게 처리할지도 정책에 넣습니다.

src/main/java/board/monitoring/ErrorBudget.java
package board.monitoring;

public record ErrorBudget(long validEvents, long badEvents, double objective) {
    public ErrorBudget {
        if (validEvents < 1 || badEvents < 0 || badEvents > validEvents) {
            throw new IllegalArgumentException("invalid SLI counts");
        }
        if (objective <= 0.0 || objective >= 1.0) {
            throw new IllegalArgumentException("invalid objective");
        }
    }

    public double observedErrorRatio() {
        return (double) badEvents / validEvents;
    }

    public double budgetFractionConsumed() {
        return observedErrorRatio() / (1.0 - objective);
    }
}

budgetFractionConsumed()는 관측한 오류율을 허용 오류율로 나눕니다. 이 값은 입력이 SLO 판정 기간 전체의 이벤트인지, 짧은 창의 이벤트인지에 따라 뜻이 달라집니다. 판정 기간 전체를 넣으면 예산을 얼마나 썼는지가 되고, 짧은 창을 넣으면 그 창에서 허용 오류율의 몇 배 속도로 실패하는지를 나타내는 소진율(burn rate)이 됩니다. 그러니 입력 기간을 결과와 함께 표시하고, 표본 수가 너무 적을 때 오류 하나가 만든 비율 경보는 최소 트래픽 조건과 함께 다룹니다.


여러 창의 소진율 경보

짧은 창의 높은 소진율은 급격한 장애를 빨리 감지하지만 잡음이 큽니다. 긴 창의 조건을 함께 요구하면 그 상태가 이어지고 있는지를 확인할 수 있습니다. 소진율이 낮지만 오래 이어지는 예산 소비는 별도의 규칙으로 감지합니다. 임계값은 다른 곳에서 복사하지 말고 목표와 대응의 긴급도에서 계산하며, 과거 데이터를 재생해 오탐을 검토합니다.

src/main/java/board/monitoring/BurnRateDecision.java
package board.monitoring;

public record BurnRateDecision(double shortWindow, double longWindow) {
    public String severity() {
        if (shortWindow >= 14.4 && longWindow >= 14.4) {
            return "PAGE_FAST_BURN";
        }
        if (shortWindow >= 6.0 && longWindow >= 3.0) {
            return "TICKET_SLOW_BURN";
        }
        return "NO_ALERT";
    }
}

실제 규칙은 서로 다른 지속 시간의 창과 최소 이벤트 수를 함께 씁니다. 숫자 하나의 임계값만으로 담당자를 호출하지 않습니다. 다음 결과 형식은 운영 규칙의 예시이며, BurnRateDecision 자체는 최소 이벤트 수 검사나 알림 전달을 구현하지 않습니다.

burn-rate 평가 실행 결과
short-window burn=18.2
long-window burn=15.1
minimum valid events satisfied=true
result=PAGE_FAST_BURN
next=runbook/board-write-slo

더 깊이: 14.4라는 숫자는 어디서 왔을까

BurnRateDecision의 14.4는 임의로 고른 값이 아니라 역산할 수 있는 값입니다. Google SRE 워크북의 SLO 경보 장은 소진율을 서비스가 SLO에 비해 오류 예산을 얼마나 빨리 쓰는지로 정의하고, 30일 판정 기간과 99.9% SLO를 예로 듭니다. 경보가 울릴 때까지 쓴 예산의 비율은 소진율에 경보 창의 길이를 곱하고 판정 기간으로 나눈 값입니다. 30일은 720시간이므로, 1시간 창에서 14.4는 14.4 × 1 ÷ 720 = 0.02, 즉 한 시간 만에 예산의 2%를 썼다는 뜻입니다.

워크북의 권장값도 같은 계산에서 나옵니다. 한 시간에 예산의 2%를 쓰면 소진율 14.4, 6시간에 5%를 쓰면 소진율 6으로 담당자를 호출하고, 3일 동안 10%를 쓰면 소진율 1로 티켓을 만듭니다. 또 긴 창의 조건이 이미 풀린 장애 뒤에도 경보가 오래 남지 않도록, 짧은 창을 긴 창의 1/12 길이로 두라고 권합니다. 1시간 창이라면 짧은 창은 5분입니다.

이 글의 BurnRateDecision이 티켓 조건으로 쓴 짧은 창 6과 긴 창 3은 워크북의 권장값을 그대로 옮긴 것이 아니라 분기를 설명하려고 고른 값입니다. 워크북에서 소진율 6은 6시간 창과 30분 창에 함께 적용하는 호출 조건이고, 워크북의 티켓 규칙 예는 24시간과 2시간 창에 3을, 3일과 6시간 창에 1을 씁니다. 그러니 판정 기간이나 SLO가 이 예와 다르다면 숫자를 그대로 가져오지 말고, 몇 시간 안에 예산의 몇 퍼센트를 쓰면 사람을 깨울지를 먼저 정한 뒤 같은 식으로 소진율을 다시 계산합니다.


대응할 수 있는 경보

경보의 애노테이션에는 영향받는 서비스와 영역, 현재 SLI, 시작 시각, 대시보드와 대응 절차(runbook)의 링크, 최근 배포, 담당 팀을 넣습니다. CPU가 높다는 경보 대신, 회원 게시판 쓰기 SLO가 빠르게 소진되고 있으며 연결 풀 포화가 함께 보인다는 식으로 증상과 보조 신호를 연결합니다. 원인에 대한 추측을 확정된 사실처럼 쓰지 않습니다.

경고와 호출의 차이는 숫자의 크기보다, 지금 사람을 깨워야 하는지에 있습니다. 자동으로 복구될 수 있는 일시적인 급증은 티켓이나 대시보드 이벤트로 남기고, 사용자 영향이 이어지며 예산을 빠르게 쓰는 경우에 담당자를 호출합니다. 같은 근본 원인에서 나온 인스턴스 수십 개의 경보는 서비스 수준으로 묶고 중복 알림을 억제합니다.


배포 변화와 수집 공백의 표시

릴리스 버전, 기능 플래그, 데이터베이스 마이그레이션, 자동 확장 이벤트를 애노테이션으로 표시하면, 변화 직후의 회귀를 빨리 찾을 수 있습니다. 다만 시점이 겹친다고 그것이 원인이라는 뜻은 아니므로, 비교할 집단과 트레이스를 확인합니다.

데이터 없음은 정상적인 0과 따로 다룹니다. Prometheus 수집 대상의 중단, 쿼리 오류, 메트릭 전송 구성 요소의 실패, 실제로 트래픽이 없는 상태를 각각 다른 상태 패널로 표시합니다. 경보 규칙 자체의 평가 실패와 알림 전달의 실패도 따로 감시합니다.

경보 전달 실패 신호
SLI query empty + target up=0 -> SCRAPE_FAILURE, not zero errors
alert rule evaluation error -> RULE_ENGINE_FAILURE
alert firing + notification attempts=0 -> ROUTING_CONFIGURATION_MISSING
notification rejected by receiver -> DELIVERY_FAILED
dashboard current + deployment marker absent -> RELEASE_METADATA_GAP

메트릭, 로그, 트레이스의 연결

메트릭은 전체의 추세를, 트레이스는 요청 하나가 거친 서비스의 스팬을, 로그는 가장 자세한 애플리케이션 이벤트를 보여 줍니다. 예시값이나 트레이스 ID 링크로 지연 시간이 높은 버킷에서 해당 트레이스로 이동하고, MDC의 상관관계 ID로 관련 로그를 찾습니다. 트레이스 ID를 메트릭 태그로 넣어 카디널리티를 폭발시키지 않습니다.

일반 로그와 오류 로그의 저장·검색 정책은 나눌 수 있지만, 같은 장애의 타임라인에서 함께 조회할 수 있어야 합니다. 민감한 페이로드는 어느 관측 도구에도 기본으로 수집하지 않습니다. 표본 추출 때문에 트레이스가 없을 수도 있으므로, 꼬리 기반 표본 추출(tail sampling)과 오류 트레이스의 보존 기간 정책을 정합니다.


정리

  • 첫 대시보드는 RED와 업무 신호, 포화도, 배포 버전, 수집 최신성을 보여 주고, 세부 화면과 트레이스·로그로 내려가는 링크를 둡니다.
  • SLI는 정상 이벤트를 유효 이벤트로 나눈 값이며, 무엇을 유효하고 정상인 이벤트로 볼지를 먼저 정합니다.
  • 오류 예산의 소진율은 짧은 창과 긴 창을 함께 보고, 최소 트래픽과 데이터 최신성을 별도 조건으로 둡니다.
  • 경보에는 영향, 현재 SLI, 대응 절차, 담당 팀을 담고, 데이터 없음과 규칙·알림 실패를 정상적인 0과 구분합니다.

연습 문제

1. 개요 대시보드와 경보 규칙

회원 게시판 개요 화면에 초당 요청 수, 오류 비율, p95, 대기량의 경과 시간, 풀 포화도, 수집 최신성, 현재 버전을 배치하세요. 99.9% 가용성 SLO에 대해 빠른 소진과 느린 소진 규칙, 최소 트래픽 조건을 만들고, 수집 대상 중단, 규칙 평가 실패, 알림 전달 실패가 각각 다른 상위 경보로 나오는지 검증하세요.

해설 보기

패널 목록보다 위에서 아래로 답해 나갈 질문을 먼저 씁니다. 담당자를 호출하는 경보에는 사용자 영향과 바로 실행할 대응 절차가 있어야 하므로, 다음처럼 필수 항목이 비면 경보를 만들 수 없게 막아 둘 수 있습니다.

package board.monitoring;

public record AlertEnvelope(
        String service,
        String severity,
        String symptom,
        String runbook,
        String owner
) {
    public AlertEnvelope {
        if (service.isBlank() || severity.isBlank() || symptom.isBlank()
                || runbook.isBlank() || owner.isBlank()) {
            throw new IllegalArgumentException("alert must be actionable");
        }
    }
}

경보를 추가한 뒤에는 과거 트래픽으로 재생해 보고, 실제 장애가 끝난 뒤에는 임계값과 대응 절차를 회고합니다.

2. 두 창의 소진율 판정

BurnRateDecision에 A는 짧은 창 18과 긴 창 2를, B는 6과 3을, C는 14.4와 14.4를 넣습니다. 각각의 반환 문자열과 그 결과를 낸 분기를 쓰세요. A의 짧은 창이 셋 가운데 가장 높다는 이유만으로 호출 경보가 되나요?

해설 보기

A는 NO_ALERT, B는 TICKET_SLOW_BURN, C는 PAGE_FAST_BURN입니다. A는 빠른 조건의 긴 창 14.4와 느린 조건의 긴 창 3을 모두 채우지 못하므로, 짧은 창이 높아도 경보가 나지 않습니다. B는 빠른 조건에서 떨어지고 느린 조건의 두 기준을 모두 채웁니다. C는 첫 분기에서 바로 반환하므로, 느린 조건도 참이지만 호출이 우선합니다.

이 함수에는 데이터 최신성, 최소 이벤트 수, 알림 전달의 성공이 없으므로, 그것까지 함께 검증해야 실제 운영 경보가 완성됩니다.

3. 예산 사용량과 소진율

ErrorBudget(1_000_000, 250, 0.999)의 budgetFractionConsumed()는 대략 얼마이며, 무엇을 뜻하나요? 이 객체가 SLO 판정 기간 전체의 이벤트라고 합니다. 별도로 짧은 창의 ErrorBudget(10_000, 50, 0.999)는 얼마이고, 이 값으로 월 예산의 500%를 썼다고 말할 수 있나요?

해설 보기

허용 실패는 1,000,000 × 0.001 = 1,000건이고, 실제 실패 250건은 그 4분의 1이므로 결과는 약 0.25, 즉 예산의 25%를 썼다는 뜻입니다. 1.0 - 0.999가 부동소수점으로 정확히 0.001이 아니어서 결과도 0.25에 아주 가까운 값으로 나오므로, 테스트에서는 오차를 허용해 비교합니다.

짧은 창의 경우는 0.005 / 0.001로 약 5이며, 그 창에서 허용 오류율의 5배 속도로 실패하고 있다는 소진율입니다. 이 값만으로 월 전체 예산의 500%를 썼다고 말할 수는 없습니다. 같은 메서드라도 분모가 판정 기간 전체인지 짧은 창인지에 따라 뜻이 바뀌므로, 입력 기간을 결과와 함께 표시합니다.

이것으로 15장과 Spring 트랙을 마칩니다. 15장에서는 Actuator 엔드포인트의 접근과 노출, 상태·정보·로그 제어, Actuator 보안에서 시작해, Micrometer 레지스트리와 Prometheus·Grafana의 수집 경로, 카운터·타이머·게이지의 쓰임, 그리고 SLO와 오류 예산에 맞춘 대시보드와 경보까지 살펴봤습니다. 처음부터 다시 훑어보고 싶다면 Spring 교재의 목차로 돌아가세요.