← 모든 글
Observability

결제 시스템 장애를 빨리 발견하려면 무엇을 봐야 할까?

고객 문의 뒤에 장애를 찾던 흐름을 Prometheus·Grafana·Micrometer 기반의 지표와 알람 중심 대응으로 바꾼 기록이다.

장애를 가장 먼저 알려주는 사람이 고객인 때가 있었다. 문의가 들어오면 로그를 찾고, 어느 결제사와 어떤 API에서 문제가 생겼는지 뒤늦게 확인했다. 로그는 있었지만 이상을 먼저 보여주는 기준이 없었다.

Prometheus와 Grafana를 설치하는 것보다 먼저 결제가 실패할 때 어떤 숫자가 달라지는지 정했다. 모니터링의 목표는 화면을 채우는 것이 아니라 고객 문의 전에 이상을 발견하고 원인 후보를 줄이는 것이었다.

서버 상태보다 결제 흐름을 먼저 봤다

CPU와 메모리는 필요했지만 결제가 정상인지 바로 답하지 못했다. 승인 요청 수, 성공률, 실패 코드, 응답 시간, 결제사별 결과를 핵심 지표로 정했다.

지표확인하려는 질문
승인 성공률사용자가 결제를 완료하고 있는가
응답 시간결제사가 느린가, 내부 처리가 느린가
결과 코드어떤 실패가 갑자기 늘었는가
처리량요청 자체가 끊겼는가

Micrometer로 애플리케이션의 말을 맞췄다

Spring Boot Actuator와 Micrometer를 사용해 같은 이름과 태그 기준으로 지표를 남겼다. 태그는 결제사, API, 결과처럼 값의 종류가 제한된 항목만 사용했다. 결제 ID처럼 계속 늘어나는 값은 메트릭에 넣지 않았다.

로그와 메트릭의 역할도 나눴다. 메트릭으로 이상이 생긴 범위를 찾고, 로그에서 개별 요청과 상태를 확인했다. 대시보드에서 바로 관련 로그 조건으로 넘어갈 수 있게 공통 필드를 맞췄다.

지표 이름보다 먼저 사건을 정의했다

승인 요청 한 건이 어떤 결과로 끝났는지를 하나의 사건으로 정의했다. 요청을 받았다는 카운터와 외부 결제사 호출 카운터를 섞지 않았다. 같은 승인 흐름에서도 사용자 결과와 내부 시도는 의미가 달랐다.

payment.approval.requests
payment.approval.results{result, provider}
payment.approval.duration{provider}
payment.gateway.calls{provider, outcome}

이름은 예시지만 원칙은 같았다. 단위와 집계 기준을 이름과 설명에 남기고, Counter와 Timer가 답하는 질문을 구분했다. 팀원이 다른 화면에서 같은 지표를 다시 만들지 않게 공통 계측 위치를 정했다.

성공과 실패의 분모도 맞췄다. 요청 수와 결과 수가 다른 지점에서 기록되면 성공률이 실제 흐름을 설명하지 못했다. 한 승인 시도의 최종 결과가 정해지는 경계에서 결과 카운터를 한 번만 올렸다.

태그의 카디널리티를 제한했다

결제사, API, 결과 코드처럼 값이 제한된 태그는 장애 범위를 줄이는 데 도움이 됐다. 반면 거래 ID, 가맹점 ID, 오류 메시지를 태그로 넣으면 시계열이 계속 늘어났다. 찾기 쉽다는 이유로 고유값을 메트릭에 넣지 않았다.

메트릭 태그대신 둔 곳
결제사 코드사용대시보드 필터
결과 분류사용성공률·오류율 집계
거래 ID사용하지 않음구조화 로그
예외 메시지사용하지 않음로그와 오류 그룹

새 태그를 추가할 때 가능한 값의 개수와 운영 질문을 함께 적었다. 질문에 답하지 못하거나 값의 상한을 설명할 수 없는 태그는 넣지 않았다. 메트릭 비용과 조회 성능도 애플리케이션 설계의 일부로 봤다.

성공률 하나로 모든 실패를 덮지 않았다

외부 결제사의 정상 거절과 시스템 오류는 사용자에게 모두 실패처럼 보이지만 대응은 달랐다. 잔액 부족 같은 비즈니스 거절, 타임아웃, 내부 예외를 같은 오류율로 묶으면 알람이 울려도 무엇을 해야 할지 알기 어려웠다.

result = SUCCESS | DECLINED | UNKNOWN | SYSTEM_ERROR
provider = bounded provider code
operation = APPROVE | CANCEL | STATUS_QUERY

결과 분류를 제한된 집합으로 만들고 알 수 없는 결과를 따로 뒀다. 타임아웃을 즉시 실패로 바꾸지 않은 이유는 외부에서는 승인이 끝났을 수 있기 때문이다. 불명확 결과는 상태 조회와 대사가 필요한 운영 신호로 취급했다.

대시보드를 세 층으로 나눴다

첫 번째 층은 사용자가 결제할 수 있는지 답했다. 두 번째 층은 어느 결제사, 어느 작업, 어느 결과에서 이상이 생겼는지 범위를 좁혔다. 세 번째 층은 JVM, 연결 풀, 데이터베이스, 외부 호출처럼 원인 후보를 확인했다.

  1. 영향: 성공률, 요청량, 응답 시간, 불명확 결과
  2. 범위: 결제사, 작업 종류, 결과 분류
  3. 원인: 애플리케이션 자원, DB, 외부 의존성

이 순서를 지키니 CPU 그래프부터 보는 습관이 줄었다. 자원 사용률이 높아도 결제가 정상일 수 있고, 자원은 정상인데 특정 결제사만 실패할 수도 있었다. 영향과 원인을 한 화면에서 섞지 않았다.

알람에는 다음 행동을 적었다

알람 메시지에는 지표값만 넣지 않았다. 어떤 사용자 영향인지, 먼저 볼 대시보드는 무엇인지, 결제사별로 범위를 좁히는 방법과 불명확 거래를 조회하는 위치를 함께 연결했다.

알람 수신
  → 사용자 영향 확인
  → 결제사·작업별 분해
  → 최근 배포·외부 장애 확인
  → 대표 요청 로그 조회
  → 불명확 거래 대사

경고와 긴급 알람도 나눴다. 관찰이 필요한 변화와 즉시 대응해야 할 실패를 같은 채널에 보내면 중요한 알람이 묻혔다. 담당자가 실제로 행동할 조건이 없는 알람은 대시보드 지표로만 남겼다.

모니터링이 보지 못하는 범위를 적었다

메트릭은 집계이므로 개별 거래의 정확성을 직접 증명하지 못했다. 성공률이 정상이어도 특정 거래의 금액이 틀릴 수 있었다. 그래서 결제와 정산 금액을 비교하는 대사와 개별 거래 조회는 별도 안전장치로 유지했다.

계측 코드 자체가 실패하거나 Prometheus 수집이 끊길 수도 있었다. 데이터 없음과 정상 0을 구분하고, 수집 상태를 별도로 확인했다. 대시보드가 조용하다는 이유만으로 시스템이 정상이라고 판단하지 않았다.

Grafana 대시보드는 대응 순서대로 배치했다

첫 화면에는 지금 결제가 가능한지를 보여주는 성공률과 응답 시간을 뒀다. 다음에는 결제사와 결과 코드로 범위를 좁히는 화면을 뒀고, 마지막에 JVM과 데이터베이스 같은 원인 지표를 배치했다.

Metric 이상
  → Alert
  → 결제 영향 확인
  → 결제사·API 범위 축소
  → 로그와 원인 지표 확인

알람은 행동과 연결했다

값이 높다는 이유만으로 알람을 만들지 않았다. 담당자가 무엇을 확인해야 하는지 정할 수 있는 지표만 알람으로 연결했다. 알람 메시지에는 대시보드, 발생 범위, 최초 확인 항목을 함께 넣었다.

대응이 끝나면 실제로 사용한 그래프와 보지 않은 그래프를 기록했다. 원인을 찾는 데 계속 쓰이지 않는 패널은 줄이고, 로그까지 이동하는 데 필요한 필터를 보완했다. 대시보드는 한 번 만들고 끝나는 문서가 아니라 장애 대응 절차의 화면이었다.

이 작업 뒤 장애 대응의 시작점이 고객 문의에서 메트릭 이상으로 바뀌었다. 모든 장애를 자동으로 설명하지는 못했지만, 적어도 어디부터 볼지 몰라 로그 전체를 뒤지는 시간은 줄었다.