로그 메트릭 트레이스는 장애를 어떻게 다른 각도에서 보여줄까? 관측 가능성의 세 단서

반응형

사용자가 결제 버튼을 눌렀는데 5초 뒤 오류가 났다고 하자. 서버 로그 한 줄만 보면 예외 메시지는 찾을 수 있어도 언제부터 얼마나 많은 요청이 느려졌는지, 데이터베이스와 외부 결제 중 어디에서 시간이 쓰였는지 알기 어렵다.

관측 가능성(observability: 시스템 밖으로 나온 신호를 이용해 내부 상태와 실패 원인을 추론할 수 있는 정도)은 데이터를 많이 쌓는 것과 다르다. 로그, 메트릭, 트레이스가 어떤 질문에 답하도록 설계됐는지가 중요하다.

로그는 한 사건의 구체적인 맥락을 남긴다

로그(log: 특정 시점에 애플리케이션이나 시스템에서 일어난 사건을 구조화해 남긴 기록)는 오류 메시지, 주문 식별자, 상태 전이, 재시도 횟수처럼 한 사건의 세부 정보를 담는다.

time=10:01:03
level=error
event=payment_confirm_failed
order_id=8421
attempt=2
error=upstream_timeout

구조화 로그(structured log: 자유 문장만 쓰지 않고 검색 가능한 필드와 값으로 남긴 로그)를 사용하면 error 문자열 검색보다 주문, 오류 종류, 서버 버전별로 묶어 볼 수 있다.

민감한 결제 정보, 비밀번호, 인증 토큰을 로그에 남기면 안 된다. 문제를 재현하는 데 필요한 식별자와 상태는 남기되 접근 권한과 보관 기한, 마스킹 규칙을 정한다.

메트릭은 시간에 따른 전체 변화를 보여 준다

메트릭(metric: 시간에 따라 수집하는 수치형 측정값)은 요청 수, 오류율, 응답 시간, 큐 길이, CPU 사용률의 변화를 빠르게 비교하게 한다.

10:00 요청률 120/s, 오류율 0.2%, p95 180ms
10:05 요청률 125/s, 오류율 8.0%, p95 4.8s

p95(95th percentile: 관측값의 95%가 이 값 이하이고 나머지 5%가 더 큰 백분위 지점)는 평균에 가려지는 느린 요청을 보는 데 유용하다. 평균 200ms여도 일부 사용자가 5초를 기다릴 수 있다.

레이블(label: 메트릭을 서비스, 상태 코드, 지역 같은 차원으로 나누는 속성)을 너무 세밀하게 만들면 시계열 수가 폭증한다. 사용자 ID나 요청 ID처럼 값 종류가 거의 무한한 항목을 레이블로 쓰면 카디널리티(cardinality: 한 레이블 조합이 만들 수 있는 서로 다른 시계열 수)가 커져 저장과 조회 비용이 급증한다. 이런 식별자는 로그나 트레이스에 두는 편이 낫다.

트레이스는 요청이 여러 구간을 지난 시간을 연결한다

분산 트레이스(distributed trace: 한 요청이 여러 서비스와 데이터베이스를 거치는 전체 경로와 시간을 연결한 기록)는 요청 하나가 어디에서 오래 머물렀는지 보여 준다.

스팬(span: 트레이스 안에서 하나의 작업 구간을 나타내며 시작·종료 시간과 부모 관계를 가진 기록)을 부모와 자식으로 연결한다.

trace: 결제 요청 4.9s
  span: API 처리 4.9s
    span: 주문 조회 40ms
    span: 결제사 호출 4.7s
    span: 결과 저장 60ms

trace ID(trace identifier: 한 요청의 모든 스팬을 묶는 식별자)를 서비스 간 헤더로 전달하면 서로 다른 서버의 작업을 같은 요청으로 연결할 수 있다. 로그에도 trace ID를 남기면 느린 스팬에서 구체적인 오류 사건으로 이동할 수 있다.

세 신호는 서로 다른 순서로 원인을 좁힌다

장애를 볼 때 한 가지 정답 순서는 없지만 다음 흐름이 유용하다.

메트릭: 언제부터, 얼마나 넓게 나빠졌는가
  ↓
트레이스: 느리거나 실패한 요청은 어느 구간에 머물렀는가
  ↓
로그: 그 구간에서 어떤 상태와 오류가 있었는가

메트릭으로 결제 오류율 상승을 찾고 해당 시간의 느린 트레이스에서 외부 호출을 좁힌 뒤, 같은 trace ID의 로그로 재시도와 응답 코드를 확인할 수 있다.

반대로 특정 사용자의 오류 제보에서 요청 ID를 얻었다면 로그에서 시작해 트레이스와 메트릭으로 영향 범위를 넓혀 갈 수 있다.

상관관계가 원인을 증명하지는 않는다

배포 직후 오류율이 올랐다고 배포가 원인일 가능성은 크지만 자동으로 증명되지는 않는다. 같은 시간에 외부 서비스 장애나 트래픽 급증이 있었을 수 있다.

배포 버전, 기능 플래그, 지역, 의존성 상태를 관측 신호에 함께 남기면 비교가 쉬워진다. 변경 전후의 동일 조건을 비교하고 되돌렸을 때 지표가 회복되는지 확인한다.

관측 데이터는 가설을 빠르게 좁히는 증거다. 로그 한 줄이나 그래프 모양만으로 인과관계를 단정하지 않는다.

샘플링과 보관 기간은 비용과 조사 가능성을 바꾼다

모든 요청의 모든 스팬을 영원히 저장하면 비용이 매우 커진다. 샘플링(sampling: 전체 요청 중 일부만 선택해 상세 트레이스를 저장하는 방식)으로 양을 줄일 수 있다.

고정 비율 샘플링은 드문 오류를 놓칠 수 있다. 오류나 느린 요청을 더 많이 남기는 tail sampling(꼬리 샘플링: 요청이 끝난 뒤 결과와 지연을 보고 저장 여부를 정하는 방식)을 사용할 수도 있다. 대신 완료된 트레이스를 잠시 모아 판단할 인프라가 필요하다.

로그와 메트릭, 트레이스의 보관 기간도 다를 수 있다. 장애 조사와 감사에 필요한 기간, 개인정보 위험, 저장 비용을 함께 정한다.

알람은 증상과 사용자 영향을 중심으로 만든다

CPU가 80%라는 이유만으로 항상 알람을 울리면 정상 배치 작업에도 깨운다. 사용자 요청의 실패율, 지연, 처리되지 못한 작업 나이처럼 서비스 목표와 연결된 증상을 먼저 본다.

SLO(Service Level Objective: 일정 기간 동안 목표로 삼는 서비스 신뢰성 수준)는 예를 들어 “정상 요청의 99.9%를 성공시키고 p95 응답을 500ms 안에 둔다”처럼 정의할 수 있다. 실제 목표 수치는 서비스 상황과 측정 방식에 따라 정해야 한다.

원인 지표는 진단에 중요하지만 알람 조건을 너무 많이 만들면 경보 피로가 생긴다. 사용자가 실제로 영향을 받는지 알려 주는 알람과 원인을 좁히는 대시보드를 분리한다.

관측 코드는 실패 경로에서도 동작해야 한다

요청이 정상 완료될 때만 로그와 스팬을 닫으면 가장 필요한 시간 초과와 취소 경로가 비어 버린다. 오류, 취소, 재시도, 큐 대기, 부분 성공 상태를 명시적으로 기록한다.

관측 시스템 자체가 느리거나 사용할 수 없을 때 애플리케이션 전체가 멈추지 않게 해야 한다. 로그 전송 버퍼와 트레이스 내보내기에도 유한한 큐와 손실 정책이 필요하다. 관측성도 백프레셔의 대상이다.

이해 확인 질문과 답변

로그가 많으면 관측 가능성이 자동으로 높아질까?

아니다. 필요한 사건과 상태가 구조화되어 있고 요청을 연결할 식별자가 있어야 한다. 같은 메시지를 반복해서 남기거나 민감 정보까지 쌓는 것은 조사 능력보다 비용과 위험을 키울 수 있다.

메트릭 레이블에 사용자 ID를 넣으면 사용자별 분석이 쉬워지지 않을까?

검색은 쉬워 보이지만 사용자 수만큼 시계열이 늘어 카디널리티가 폭증한다. 사용자·주문 같은 고유 식별자는 로그나 트레이스에 두고 메트릭은 유한한 서비스·상태·지역 차원으로 집계한다.

트레이스 하나만 보면 장애 원인을 확정할 수 있을까?

한 요청의 경로와 느린 구간은 볼 수 있지만 전체 영향 범위와 반복 여부는 알기 어렵다. 메트릭으로 범위를 보고 로그로 구체 상태를 확인해야 한다.

모든 트레이스를 저장하지 않으면 오류를 놓치지 않을까?

놓칠 수 있다. 그래서 일반 요청은 일부만 저장하고 오류·느린 요청은 더 많이 남기는 샘플링 정책을 쓸 수 있다. 조사 요구와 비용에 맞춰 보관 정책을 정한다.

AI에게 이렇게 요청할 수 있다

결제 요청의 오류율과 p95가 갑자기 높아진 상황을 메트릭, 분산 트레이스, 구조화 로그로 차례로 좁혀 가는 예를 보여 줘. trace ID로 스팬과 로그를 연결하고, 높은 카디널리티 레이블과 샘플링 때문에 무엇을 놓칠 수 있는지도 설명해 줘.

관측 가능성은 로그를 많이 남기는 습관이 아니다. 언제부터 얼마나 넓게 나빠졌는지, 어느 경로에서 시간이 쓰였는지, 그 지점에서 어떤 상태가 있었는지를 서로 연결해 설명할 수 있게 만드는 설계다.

반응형
KEEP READING
카테고리 전체 보기 →

댓글