본문 바로가기

Prometheus·Grafana

Micrometer 측정을 Prometheus가 수집하고 Grafana가 시각화하는 경로를 연결해 비율·히스토그램·레이블 비용을 검증합니다.

애플리케이션의 레지스트리에 값이 보인다고 대시보드가 저절로 채워지지는 않습니다. Prometheus용 레지스트리가 미터를 Prometheus 형식의 텍스트로 만들고, Actuator 엔드포인트가 그것을 노출하며, Prometheus가 그 대상을 주기적으로 수집하고, Grafana가 시계열을 쿼리해야 그래프가 그려집니다.

어느 단계가 끊겨도 패널은 비어 보이지만, 원인과 복구 방법은 단계마다 다릅니다. 이 글에서는 수집 엔드포인트와 이름 변환, 카운터와 히스토그램 쿼리, 애노테이션 계측, Grafana 패널의 의미를 차례로 다루고, 빈 패널을 단계별로 진단하는 방법을 정리합니다.

패널의 선은 계측·노출·수집·쿼리 전 구간이 최신 상태일 때만 의미가 있다

레지스트리·엔드포인트·Prometheus pull·Grafana query에서 같은 관측 값이 어디까지 준비됐는지 달라진다.

PROMETHEUS · SCRAPE AND QUERY

빈 패널은 레지스트리부터 쿼리까지 경계를 좁혀 진단한다

Prometheus는 엔드포인트를 주기적으로 가져오고 Grafana는 저장된 시계열을 쿼리합니다. 데이터 없음과 정상 트래픽 0을 구분합니다.

수집과 조회 요청 방향을 데이터 이동과 구분한다실선은 레지스트리→엔드포인트→Prometheus→Grafana의 데이터 이동이다. 점선은 Prometheus가 엔드포인트를 가져오는 요청과 Grafana가 Prometheus를 조회하는 요청이다. 애플리케이션의 능동 push를 뜻하지 않는다. 같은 4노드5관계를 모바일에서도 유지한다. 실제 auth/TLS/웹노출·이름·시간 범위는 보조 패널과 본문에서 확인한다.레지스트리실제 미터수집 엔드포인트노출 텍스트Prometheus수집·저장Grafana쿼리·표시수집 요청조회 요청
수집과 조회 요청 방향을 데이터 이동과 구분한다실선은 레지스트리→엔드포인트→Prometheus→Grafana의 데이터 이동이다. 점선은 Prometheus가 엔드포인트를 가져오는 요청과 Grafana가 Prometheus를 조회하는 요청이다. 애플리케이션의 능동 push를 뜻하지 않는다. 같은 4노드5관계를 모바일에서도 유지한다. 실제 auth/TLS/웹노출·이름·시간 범위는 보조 패널과 본문에서 확인한다.레지스트리실제 미터수집 엔드포인트노출 텍스트Prometheus수집·저장Grafana쿼리·표시수집 요청조회 요청

앱 · 계측과 노출

  1. 레지스트리의 실제 미터

    이름·제한된 태그·값을 확인합니다. 애노테이션은 aspect와 proxy가 필요하고 자기 호출·직접 new는 별도 시험합니다.

  2. Prometheus exposition

    레지스트리 의존성·access·웹 노출·보안을 확인하고 실제 HELP/TYPE/표본 이름과 레이블을 읽습니다.

  3. 중복과 표본 경계

    같은 사건에 수동과 애노테이션 계측을 겹치지 않습니다. 15초 수집은 2초 게이지 급증을 놓칠 수 있습니다.

수집 · 저장과 조회

  1. Prometheus Targets

    인증·TLS·service discovery와 최근 scrape, 지속 시간, last error, up을 확인합니다. 브라우저 200과 주기 수집은 다른 증거입니다.

  2. 원시 PromQL

    counter는 rate/increase로 reset을 처리합니다. 수집 간격에 비해 충분한 창을 사용하고 필요한 레이블만 집계합니다.

  3. Grafana 패널

    실제 쿼리 결과 뒤에 단위·시간 범위·변환·범례를 확인합니다. 빈 결과를 0으로 채우지 않고 최신성을 함께 표시합니다.

여러 인스턴스 p95는 발행한 히스토그램 버킷을 올바르게 집계해 계산합니다. 클라이언트 백분위수를 단순 평균하지 않습니다. 수집·쿼리·표시 주체와 인증 정보는 별도입니다.

이 예의 조건

  • 두 박스는 같은 실행의 진행 정보와 자료·결과를 관찰하는 고정 역할입니다. 박스의 좌우·위아래 위치를 별도 호출 순서로 해석하지 않습니다.
  • 조건별 종료는 대안입니다. 실패 뒤 정상 후속 단계를 실행하거나 독립 비교 사례를 같은 요청으로 연결하지 않습니다.
  • 실선은 레지스트리→엔드포인트→Prometheus→Grafana의 데이터 이동이다. 점선은 Prometheus가 엔드포인트의 자료를 가져오는 요청과 Grafana가 Prometheus를 조회하는 요청이다. 애플리케이션의 능동 push를 뜻하지 않는다. 같은 4노드와 5관계를 모바일에서도 유지한다. 실제 auth/TLS/웹 노출·이름·시간 범위는 보조 패널과 본문에서 확인한다.
관계도의 보충 설명
원래 전체 그림의 표시 문구·조건·주석
순번원문 표시
1패널의 선은 계측·노출·수집·쿼리 전 구간이 최신 상태일 때만 의미가 있다
4Prometheus는 엔드포인트의 자료를 주기적으로 가져오고 Grafana는 저장된 시계열을 쿼리합니다. 데이터 없음과 정상 트래픽 0을 구분합니다.
6실선은 레지스트리→엔드포인트→Prometheus→Grafana의 데이터 이동이다. 점선은 Prometheus가 엔드포인트의 자료를 가져오는 요청과 Grafana가 Prometheus를 조회하는 요청이다. 애플리케이션의 능동 push를 뜻하지 않는다. 같은 4노드와 5관계를 모바일에서도 유지한다. 실제 auth/TLS/웹 노출·이름·시간 범위는 보조 패널과 본문에서 확인한다.
단계별 상태·값과 전환 이유
장면관찰 값전환 이유
관측 값 준비

registry 값이 노출 endpoint에 준비됩니다.

자료: registry→endpoint · 웹 노출·auth: 별도 조건수집할 값의 노출 경계가 생깁니다.
Prometheus pull

Prometheus가 endpoint에 요청하여 자료를 가져옵니다.

요청: Prometheus→endpoint · 전달: 능동 push 아님수집 자료가 Prometheus에 도달합니다.
Grafana query

Grafana가 Prometheus를 조회합니다.

요청: Grafana→Prometheus · 조건: 이름·시간 범위 일치전 구간에서 최신 상태인 자료만 패널에 나타납니다.

패널의 선은 계측·노출·수집·쿼리 전 구간이 최신 상태일 때만 의미가 있다

레지스트리·엔드포인트·Prometheus pull·Grafana query에서 같은 관측 값이 어디까지 준비됐는지 달라진다.

PROMETHEUS · SCRAPE AND QUERY

빈 패널은 레지스트리부터 쿼리까지 경계를 좁혀 진단한다

Prometheus는 엔드포인트를 주기적으로 가져오고 Grafana는 저장된 시계열을 쿼리합니다. 데이터 없음과 정상 트래픽 0을 구분합니다.

수집과 조회 요청 방향을 데이터 이동과 구분한다실선은 레지스트리→엔드포인트→Prometheus→Grafana의 데이터 이동이다. 점선은 Prometheus가 엔드포인트를 가져오는 요청과 Grafana가 Prometheus를 조회하는 요청이다. 애플리케이션의 능동 push를 뜻하지 않는다. 같은 4노드5관계를 모바일에서도 유지한다. 실제 auth/TLS/웹노출·이름·시간 범위는 보조 패널과 본문에서 확인한다.레지스트리실제 미터수집 엔드포인트노출 텍스트Prometheus수집·저장Grafana쿼리·표시수집 요청조회 요청
수집과 조회 요청 방향을 데이터 이동과 구분한다실선은 레지스트리→엔드포인트→Prometheus→Grafana의 데이터 이동이다. 점선은 Prometheus가 엔드포인트를 가져오는 요청과 Grafana가 Prometheus를 조회하는 요청이다. 애플리케이션의 능동 push를 뜻하지 않는다. 같은 4노드5관계를 모바일에서도 유지한다. 실제 auth/TLS/웹노출·이름·시간 범위는 보조 패널과 본문에서 확인한다.레지스트리실제 미터수집 엔드포인트노출 텍스트Prometheus수집·저장Grafana쿼리·표시수집 요청조회 요청

앱 · 계측과 노출

  1. 레지스트리의 실제 미터

    이름·제한된 태그·값을 확인합니다. 애노테이션은 aspect와 proxy가 필요하고 자기 호출·직접 new는 별도 시험합니다.

  2. Prometheus exposition

    레지스트리 의존성·access·웹 노출·보안을 확인하고 실제 HELP/TYPE/표본 이름과 레이블을 읽습니다.

  3. 중복과 표본 경계

    같은 사건에 수동과 애노테이션 계측을 겹치지 않습니다. 15초 수집은 2초 게이지 급증을 놓칠 수 있습니다.

수집 · 저장과 조회

  1. Prometheus Targets

    인증·TLS·service discovery와 최근 scrape, 지속 시간, last error, up을 확인합니다. 브라우저 200과 주기 수집은 다른 증거입니다.

  2. 원시 PromQL

    counter는 rate/increase로 reset을 처리합니다. 수집 간격에 비해 충분한 창을 사용하고 필요한 레이블만 집계합니다.

  3. Grafana 패널

    실제 쿼리 결과 뒤에 단위·시간 범위·변환·범례를 확인합니다. 빈 결과를 0으로 채우지 않고 최신성을 함께 표시합니다.

여러 인스턴스 p95는 발행한 히스토그램 버킷을 올바르게 집계해 계산합니다. 클라이언트 백분위수를 단순 평균하지 않습니다. 수집·쿼리·표시 주체와 인증 정보는 별도입니다.

이 예의 조건

  • 두 박스는 같은 실행의 진행 정보와 자료·결과를 관찰하는 고정 역할입니다. 박스의 좌우·위아래 위치를 별도 호출 순서로 해석하지 않습니다.
  • 조건별 종료는 대안입니다. 실패 뒤 정상 후속 단계를 실행하거나 독립 비교 사례를 같은 요청으로 연결하지 않습니다.
  • 실선은 레지스트리→엔드포인트→Prometheus→Grafana의 데이터 이동이다. 점선은 Prometheus가 엔드포인트의 자료를 가져오는 요청과 Grafana가 Prometheus를 조회하는 요청이다. 애플리케이션의 능동 push를 뜻하지 않는다. 같은 4노드와 5관계를 모바일에서도 유지한다. 실제 auth/TLS/웹 노출·이름·시간 범위는 보조 패널과 본문에서 확인한다.

관계도의 보충 설명

원래 전체 그림의 표시 문구·조건·주석
순번원문 표시
1패널의 선은 계측·노출·수집·쿼리 전 구간이 최신 상태일 때만 의미가 있다
4Prometheus는 엔드포인트의 자료를 주기적으로 가져오고 Grafana는 저장된 시계열을 쿼리합니다. 데이터 없음과 정상 트래픽 0을 구분합니다.
6실선은 레지스트리→엔드포인트→Prometheus→Grafana의 데이터 이동이다. 점선은 Prometheus가 엔드포인트의 자료를 가져오는 요청과 Grafana가 Prometheus를 조회하는 요청이다. 애플리케이션의 능동 push를 뜻하지 않는다. 같은 4노드와 5관계를 모바일에서도 유지한다. 실제 auth/TLS/웹 노출·이름·시간 범위는 보조 패널과 본문에서 확인한다.
단계별 상태·값과 전환 이유
장면관찰 값전환 이유
관측 값 준비

registry 값이 노출 endpoint에 준비됩니다.

자료: registry→endpoint · 웹 노출·auth: 별도 조건수집할 값의 노출 경계가 생깁니다.
Prometheus pull

Prometheus가 endpoint에 요청하여 자료를 가져옵니다.

요청: Prometheus→endpoint · 전달: 능동 push 아님수집 자료가 Prometheus에 도달합니다.
Grafana query

Grafana가 Prometheus를 조회합니다.

요청: Grafana→Prometheus · 조건: 이름·시간 범위 일치전 구간에서 최신 상태인 자료만 패널에 나타납니다.

그림의 실선은 레지스트리에서 수집 엔드포인트, Prometheus, Grafana로 데이터가 옮겨 가는 방향입니다. 점선은 Prometheus가 엔드포인트에 수집을 요청하고, Grafana가 Prometheus에 조회를 요청하는 방향이며, 애플리케이션이 스스로 데이터를 밀어 보낸다는 뜻이 아닙니다. 아래 칸들은 애플리케이션 쪽의 계측과 노출에서 확인할 것과, 수집·저장·조회 쪽에서 확인할 것을 나눠 정리합니다.


Prometheus 수집 엔드포인트

io.micrometer:micrometer-registry-prometheus 의존성과 Actuator가 있으면 /actuator/prometheus 엔드포인트가 만들어질 후보가 됩니다. Actuator 엔드포인트의 build.gradle에는 이 의존성이 없으므로 runtimeOnly 'io.micrometer:micrometer-registry-prometheus'처럼 버전 없이 더하고, 버전은 Boot BOM이 정하게 합니다. 실제로 수집되려면 웹 노출 허용 목록, 관리 보안, 서비스 탐색 대상을 모두 통과해야 합니다. 브라우저에서 한 번 200을 받았다는 것보다, Prometheus의 Targets 화면에서 최근 수집 시각, 소요 시간, 오류, up 값을 확인하는 것이 중요합니다.

Spring 애플리케이션의 설정은 Prometheus 엔드포인트를 웹에 노출합니다.

src/main/resources/application.yml
management:
  endpoints:
    web:
      exposure:
        include: "health,prometheus"

이 설정의 노출 목록에는 health와 prometheus만 있으므로, Actuator 엔드포인트에서 열었던 info와 metrics는 이 설정으로는 웹에 노출되지 않습니다. Prometheus 쪽에서는 수집할 경로와 간격, 대상을 적습니다. metrics_path를 적지 않으면 Prometheus는 기본 경로인 /metrics를 요청하므로, Actuator의 경로를 여기에 명시해야 합니다.

prometheus.yml
scrape_configs:
  - job_name: board
    metrics_path: /actuator/prometheus
    scrape_interval: 15s
    static_configs:
      - targets: ["board-management:9091"]

운영 환경에서는 정적 대상보다 오케스트레이터의 서비스 탐색을 쓰고, URL 스킴, 인증, TLS, 레이블 재작성을 함께 구성합니다. 수집 간격을 15초로 두면, 2초 동안 일어난 순간적인 급증이 평균이나 게이지 표본에서 사라질 수 있다는 점도 이해해 둡니다.


메트릭 이름과 레이블의 변환

Micrometer 레지스트리에서 baseUnit("entries")로 등록한 board.entry.processed 카운터는, 기본 Prometheus 이름 규칙에서 board_entry_processed_entries_total로 노출됩니다. 미터의 태그는 레이블이 됩니다. 이름 규칙이나 필터를 따로 설정했다면, 아래 표본의 이름과 실제 노출 결과를 대조합니다.

Prometheus exposition 예
# HELP board_entry_processed_entries_total Completed entry processing attempts
# TYPE board_entry_processed_entries_total counter
board_entry_processed_entries_total{outcome="accepted"} 42.0
board_entry_processed_entries_total{outcome="rejected"} 3.0

대시보드 쿼리를 자바 코드의 이름만 보고 작성하지 말고, 실제 노출 형식의 HELP, TYPE, 표본 이름, 레이블을 확인합니다. 레이블 값에 게시글 ID를 넣으면 수집 페이로드와 백엔드의 시계열이 계속 늘어납니다. 경로, 메서드, 정해진 결과 코드처럼 집계할 수 있는 레이블을 고르고, 원시 식별자는 트레이스나 로그로 보냅니다.

더 깊이: 브라우저로 본 형식과 Prometheus가 받는 형식은 다를 수 있습니다

같은 레지스트리도 두 가지 텍스트 형식으로 내보낼 수 있습니다. Micrometer의 Prometheus 문서에 따르면 PrometheusMeterRegistry.scrape()는 기본으로 Prometheus 텍스트 형식을 돌려주고, application/openmetrics-text 콘텐츠 타입을 주면 OpenMetrics 형식을 돌려줍니다. Spring Boot의 prometheus 엔드포인트도 두 형식을 모두 지원하며, 특정 Accept 헤더가 없으면 Prometheus 텍스트 형식으로 응답합니다. 표본에 덧붙이는 메타데이터인 예시값(exemplar)은 OpenMetrics 형식에만 나타나므로, 헤더 없이 확인한 응답에 예시값이 없다고 해서 수집기에도 없는 것은 아닙니다.

반대로 Prometheus 서버는 수집할 때 형식을 협상합니다. Prometheus 설정 문서는 scrape_protocols를 수집 중에 협상할 프로토콜 목록으로 설명하며, 네이티브 히스토그램 수집을 끈 기본 상태에서 그 순서가 OpenMetricsText1.0.0, OpenMetricsText0.0.1, PrometheusText1.0.0, PrometheusText0.0.4라고 적습니다. 즉 수집기는 OpenMetrics를 먼저 원하므로, 장애를 진단할 때는 수집기와 같은 형식을 요청해 응답을 비교하는 편이 정확합니다. 또 Micrometer는 새 Prometheus Java 클라이언트(1.x)용 micrometer-registry-prometheus와 이전 클라이언트(0.x)용 micrometer-registry-prometheus-simpleclient를 함께 지원하는데, Spring Boot 4.1.2의 BOM도 두 클라이언트의 버전을 모두 관리하므로 의존성 그래프에서 어느 쪽이 들어왔는지 확인합니다.


카운터 쿼리

누적 카운터의 값 42만으로는 최근 트래픽을 알 수 없습니다. rate(...[5m])는 초당 평균 증가율을, increase(...[1h])는 그 구간의 증가량을 묻습니다. 프로세스가 재시작하면서 값이 0으로 내려가는 초기화는 PromQL 함수가 처리하므로, 게이지처럼 마지막 값끼리 단순히 빼지 않습니다.

구간이 수집 간격에 비해 너무 짧으면 표본이 부족해 그래프가 흔들립니다. 경보에는 빠른 반응과 안정성 사이의 균형을 생각해 여러 구간을 함께 씁니다. sum by (outcome)처럼 필요한 레이블만 남기고, 인스턴스를 합칠지 따로 볼지는 패널의 목적에 맞춥니다.

위 job_name: board의 허용과 거부 두 결과만 분모에 넣는 비율이라면, 실제 노출 이름이 앞의 표본과 같고 필요한 시계열이 있다는 조건에서 다음 PromQL로 인스턴스를 합칠 수 있습니다.

board 작업의 최근 5분 허용 비율
sum(rate(board_entry_processed_entries_total{job="board",outcome="accepted"}[5m]))
/
sum(rate(board_entry_processed_entries_total{job="board",outcome=~"accepted|rejected"}[5m]))

Prometheus의 함수 설명에 따르면 increase는 카운터의 초기화를 보정하고 구간의 양 끝까지 값을 외삽하므로, 정수 카운터에서도 소수가 나올 수 있습니다. 그래서 increase가 화면에 보여 주는 숫자를 영속적인 사건 원장의 정확한 건수로 단정하지 않습니다.

src/main/java/board/metrics/Outcome.java
package board.metrics;

public enum Outcome {
    ACCEPTED("accepted"),
    REJECTED("rejected"),
    FAILED("failed");

    private final String tagValue;

    Outcome(String tagValue) {
        this.tagValue = tagValue;
    }

    public String tagValue() {
        return tagValue;
    }
}

레이블 값을 열거형에서 만들면 오타나 임의의 예외 이름이 들어오는 것을 막을 수 있습니다.


타이머의 지연 분포

타이머의 호출 개수와 전체 시간만 있으면 평균은 계산할 수 있지만, 느린 소수의 요청이 평균 속에 숨습니다. 서비스 수준의 지연 시간을 판단하려면 히스토그램 버킷을 발행하고, histogram_quantile로 전체 인스턴스를 합친 백분위수를 계산하거나 SLO 기준 버킷에 든 비율을 씁니다. 클라이언트 쪽에서 계산한 백분위수는 인스턴스끼리 합산하기 어렵다는 점도 구별합니다.

모든 URI와 상태 레이블에 버킷을 무분별하게 켜면 시계열 수가 버킷 개수만큼 곱해집니다. 중요한 작업에만 SLO 기준을 설정하고, 실제 트래픽의 범위에 맞춰 버킷을 조정합니다. 최댓값은 시간 구간이 지나면 사라질 수 있으므로, 영구적인 최악값으로 해석하지 않습니다.

클래식 히스토그램을 실제로 발행했고 내보낸 이름과 le 레이블을 확인했다면, 인스턴스의 버킷 증가율을 먼저 합친 뒤 백분위수를 계산합니다. 여기서는 다음 글의 PostImportMetrics가 기록하는 board.import.latency가 board_import_latency_seconds_bucket으로 노출된 경우를 가정합니다. 앞 글의 ImportTimer에는 publishPercentileHistogram()이 없으므로, 그 타이머에 버킷이 있다고 가정하지 않습니다.

발행된 클래식 가져오기 히스토그램의 p95
histogram_quantile(0.95,
  sum by (le) (rate(board_import_latency_seconds_bucket{job="board"}[5m])))

클래식 버킷을 집계할 때는 le를 남겨야 합니다. 결과는 버킷 안의 분포를 추정한 백분위수이고, 관측이 0건이면 NaN이 될 수 있습니다. 인스턴스별 p95를 평균해 전체 p95를 계산하지 않습니다.


@Counted와 @Timed 프록시

애노테이션 계측은 업무 코드에 계측 코드가 섞이는 것을 줄여 주지만, 애스펙트 빈, 프록시 적용 가능성, 포인트컷, 자기 호출이라는 제약이 따릅니다. Spring Boot에서는 management.observations.annotations.enabled를 true로 켜고(기본값은 false입니다), org.aspectj:aspectjweaver도 클래스 경로에 있어야 이 애노테이션들을 처리하는 구성이 자동으로 적용됩니다. Actuator 엔드포인트에서 둔 두 스타터에는 이 라이브러리가 들어 있지 않으므로, 로깅·재시도 AOP에서 aspectjweaver를 이미 더하지 않았다면 이 라이브러리를 담은 implementation 'org.springframework.boot:spring-boot-starter-aspectj'를 버전 없이 더합니다.

src/main/java/board/metrics/MeasuredImportService.java
package board.metrics;

import io.micrometer.core.annotation.Counted;
import io.micrometer.core.annotation.Timed;
import org.springframework.stereotype.Service;

@Service
public class MeasuredImportService {
    @Counted(value = "board.import.invocations", extraTags = {"format", "markdown"})
    @Timed(value = "board.import.duration", extraTags = {"format", "markdown"})
    public int importMarkdown(String body) {
        if (body.isBlank()) {
            throw new IllegalArgumentException("empty markdown");
        }
        return body.lines().mapToInt(line -> 1).sum();
    }
}

애노테이션의 이름과 태그 규칙도 코드 검토 대상이며, 실패와 예외 태그의 값이 제한되어 있는지 확인합니다. 이 예제의 board.import.duration은 앞 글의 ImportTimer와 이름이 같지만, 애노테이션 쪽은 다른 태그 키를 붙이므로 한 애플리케이션에서 둘을 함께 쓰면 같은 이름에 서로 다른 태그 키 집합이 섞입니다. 같은 사건에 수동 계측과 애노테이션 계측을 함께 적용해 두 번 기록하지도 않습니다. 자기 호출이나 직접 new로 만든 객체에서 애노테이션이 동작하지 않는다는 점은 별도의 런타임 테스트로 확인합니다.


Grafana 패널의 의미

패널 제목에는 주문 수 같은 지표 이름 대신, 최근 5분의 허용 비율이 기준선에서 벗어났는지처럼 그 패널이 답하려는 질문을 씁니다. 쿼리, 범례, 단위, 최솟값과 최댓값, 임계값, 대응 절차의 링크를 버전 관리합니다. 바이트를 십진 단위와 이진 단위 가운데 무엇으로 표시할지 정하고, 지연 시간의 초를 밀리초로 잘못 표시하지 않습니다.

0과 데이터 없음은 다릅니다. 수집이 끊긴 패널을 0으로 채우면, 트래픽이 사라진 장애를 정상적인 유휴 상태로 오해합니다. up, 표본이 얼마나 오래됐는지, 애플리케이션 메트릭을 함께 배치하고, 배포 시점을 애노테이션으로 표시해 변화가 언제 시작됐는지 보이게 합니다.


수집 실패 진단

빈 패널은 단계마다 다른 원인을 가집니다. 아래 예는 데이터가 지나가는 순서대로 애플리케이션 레지스트리, 노출 엔드포인트, 수집 대상, Prometheus 쿼리, 표본의 나이를 확인했을 때 처음 어긋나는 지점과 그 분류를 적은 것입니다. 앞 단계가 정상이어야 다음 줄의 판정이 의미가 있으므로, 위에서부터 차례로 확인합니다.

빈 Grafana panel 진단
application registry has meter=false -> INSTRUMENTATION_MISSING
prometheus endpoint lacks sample -> EXPORT_FILTER_OR_NAMING
target up=0 -> SCRAPE_AUTH_NETWORK_FAILURE
Prometheus query returns series=true, panel empty=true -> GRAFANA_QUERY_OR_TIME_RANGE
sample age > 2 * scrape interval -> STALE_DATA

대시보드만 새로 고침하지 말고, 애플리케이션 레지스트리에서 시작해 노출 형식, 수집 대상, 원시 쿼리, 패널의 변환 순서로 따라갑니다.


정리

  • 대시보드가 채워지려면 레지스트리, 수집 엔드포인트, Prometheus의 수집, Grafana의 쿼리가 모두 이어져야 하며, 수집은 Prometheus가 요청하는 방식입니다.
  • 카운터는 rate와 increase로 초기화를 처리해 읽고, increase의 값을 정확한 사건 건수로 단정하지 않습니다.
  • 여러 인스턴스의 백분위수는 발행한 히스토그램 버킷을 le별로 합친 뒤 histogram_quantile로 계산합니다.
  • @Counted와 @Timed는 애스펙트와 프록시가 필요하며, 같은 사건에 수동 계측과 겹쳐 쓰지 않습니다.

연습 문제

1. 수집에서 패널까지

회원 게시판의 허용·거부 카운터와 가져오기 타이머를 Prometheus로 수집하세요. 최근 5분의 비율, 1시간 increase, p95 지연 시간을 Grafana 패널로 만들고, 인스턴스 초기화와 수집 대상 중단을 재현하세요. 애노테이션 방식에서는 프록시 호출과 자기 호출에서 미터의 개수가 어떻게 다른지 검증하세요.

해설 보기

패널을 만들기 전에 원시 PromQL의 결과와 레이블 집합을 먼저 확인합니다. 카운터에는 초기화를 처리하는 함수를 쓰고, 타이머에는 여러 인스턴스를 합칠 수 있는 히스토그램을 씁니다. 데이터가 최신인지 판단하는 보조 타입은 다음과 같이 둘 수 있습니다.

package board.metrics;

import java.time.Duration;

public record ScrapeFreshness(Duration interval, Duration age) {
    public boolean stale() {
        return age.compareTo(interval.multipliedBy(2)) > 0;
    }

    public String displayState() {
        return stale() ? "NO_CURRENT_DATA" : "CURRENT_DATA";
    }
}

트래픽 0과 수집 중단을 같은 색으로 표시하지 않습니다.

2. 최신성과 비율

ScrapeFreshness의 interval을 15초로 둡니다. age가 30초일 때와 31초일 때의 displayState()를 구하세요. 앞의 비율 쿼리에서 허용이 초당 6건, 거부가 초당 4건이면 percent로 무엇이 표시되나요? 두 증가율이 모두 0일 때는 무엇을 뜻하나요?

해설 보기

30초는 CURRENT_DATA, 31초는 NO_CURRENT_DATA입니다. 비교가 간격의 두 배보다 큰 경우만 오래된 것으로 보기 때문입니다.

비율은 6 / (6 + 4) = 0.6이므로 60%로 표시됩니다. 두 증가율이 모두 0이면 분모가 0이라 비율을 계산할 수 없습니다. 이 값을 0%로 채우지 말고, 수집 상태와 절대 트래픽을 확인해 실제로 트래픽이 없는 것인지 데이터가 비어 있는 것인지 구분합니다. 시계열 자체가 없는 경우와도 따로 다룹니다.

3. 빈 패널의 원인 좁히기

다음 두 상황에서 각각 어느 단계를 의심해야 하나요? 첫째는 애플리케이션 레지스트리와 /actuator/prometheus에 표본이 있는데, Prometheus Targets에서 up이 0입니다. 둘째는 Prometheus에서 직접 쿼리하면 시계열이 나오는데, Grafana 패널은 비어 있습니다.

해설 보기

첫째는 노출까지는 됐지만 수집이 실패한 경우이므로, 수집 인증, TLS, 네트워크, 대상 주소를 확인합니다. 진단 표의 SCRAPE_AUTH_NETWORK_FAILURE에 해당하며, 애플리케이션의 계측 코드를 고칠 일은 아닙니다.

둘째는 저장된 데이터는 있는데 표시 단계에서 끊긴 경우이므로, Grafana의 데이터 소스, 쿼리 문자열, 시간 범위, 패널의 변환을 확인합니다. 진단 표의 GRAFANA_QUERY_OR_TIME_RANGE에 해당합니다. 이렇게 앞 단계부터 결과가 있는지 확인해 가면, 원인을 한 단계로 빠르게 좁힐 수 있습니다.

다음 글 카운터·타이머·게이지에서는 세 미터 타입이 각각 어떤 사건과 상태를 표현하는지 구분하고, 게시판 가져오기 기능에 맞는 타입을 고르는 방법을 다룹니다.