유저: 실습 기능이 너무 느린것 같아요.
개발팀: 저희는 재연이 안되어서... 혹시 어떻게 하셨나요?

 

이런 일이 벌써 몇 번째였다. 항상 유저가 먼저 문제를 발견하고, 리포트해주기를 기다리는 수동적인 상황에 놓여있었다. 서비스에 문제가 생겨도 개발자는 알 수 없는 구조였다.

그러다 보니 자연스레 유저가 서비스를 신뢰하지 못하게 되고, 개발자들은 예측이 안되는 이슈에 예정된 일을 내팽개치고 달려나와 해결해야 하는 상황에 놓이게 되었다.

더 큰 문제는 개선 작업 후에도 마찬가지였다. 데이터베이스 쿼리를 최적화하고, 캐시를 도입해도 "정말 빨라졌나?"라는 의문만 남긴채 쌓인 이슈를 처리하러 갈 뿐이었다.

개선 효과를 수치적으로 증명할 방법이 없기 때문이었는데, 이는 팀 내 개발자들의 동기부여에도 악영향을 미치고 있었다.

 


메트릭부터, 빠르게 구축하자

APM(Application Performance Monitoring) 시스템을 한번에 도입하려 했지만, 복잡한 인프라 세팅과 러닝 커브가 부담스러웠다. 우리에게 당장 필요한 현재 서버 상태를 대략적으로라도 파악할 수 있는 메트릭부터 구축하기로 결정했다.

메트릭만 제대로 도입해도 위에서 설명한 문제들을 해결 할 수 있다

  • 실시간 서비스 상태 파악: 개발자가 먼저 서비스 이상을 탐지할 수 있음
  • 성능 개선 효과 측정: 수치로 증명 가능한 개선 결과
  • 문제 구간 식별: 어떤 엔드포인트에서 문제가 발생하는지 즉시 확인

 


수집하기

아래 두가지 메트릭만 우선 수집하기로 결정했다.

http_requests_total (요청 수)

  • 라벨: path, method, status_code
  • 엔드포인트별 트래픽 패턴과 에러율 추적

http_duration (요청 시간)

  • 라벨: path, method
  • 각 요청의 처리 시간 분포 측정

현재 우리의 대부분 MSA는 FastAPI로 구현되어 있는데, http 요청 메트릭을 기록하는 Middleware를 구현하였다

def setup_apm(app: FastAPI, service_name: str, version: str) -> MetricsManager:
    middleware = APMMiddleware(service_name, version)
    app.add_middleware(BaseHTTPMiddleware, dispatch=middleware)

    @app.get(
        "/metrics",
        include_in_schema=False,
        response_class=PlainTextResponse,
        tags=["monitoring"],
    )
    async def metrics_endpoint() -> PlainTextResponse:
        return PlainTextResponse(
            middleware.metrics.generate_metrics(),
            media_type="text/plain; version=0.0.4; charset=utf-8",
        )

    return middleware.metrics

 


 

시각화 (대시보드 구축)

시각화는 grafana를 이용했다.
grafana는 JSON 기반 대시보드 설정이 가능해서, claude와 대화 몇번 주고받으면서 대시보드를 완성시켰다

 

Request Per Seconds.
서비스 전체의 트래픽 패턴을 모니터링한다. 어느 기능에 요청이 몰리는지 파악할 수 있다.

Request Duration (99p/90p/50p)
응답 시간의 백분위수를 추적하는 가장 중요한 메트릭이다

  • 50p (median): 일반적인 사용자 경험
  • 90p: 대부분의 사용자 경험
  • 99p: 최악의 사용자 경험

Slowest Endpoint
가장 느린 엔드포인트가 무엇인지 알 수 있다. 성능 개선이 필요한 기능의 우선순위를 정하는 데 도움이 된다.


배포 하자마자 문제발견

배포하고 1시간만에 안되는 조회 Endpoint에서 99p와 90p, 50p의 격차가 넓은것을 확인했다.
외부호출이 있는 엔드포인트 였는데 특정 상황에 지연이 발생하고 있어서, 내일 해결해봐야 겠다.


느낀점

  • 이걸 왜 이제 했을까..
  • 평균은 문제를 숨긴다
    • 대시보드를 세팅할때 처음에는 Request Duration의 평균값만 기록하였는데, 모든 기능의 지연시간이 고만고만해보여서 아무런 문제가 없는줄 알았다.
    • 의심스러워서 99p/90p/50p등의 백분위수를 도입하고 다니 평균에 가려져있던 문제들이 드러나게 되었다.
  • 수치화는 개발자에게 동기부여를 준다
    • 대부분의 개발자는 목표지향적인 성격을 가지고 있다고 생각한다
    • 그런 그들에게 OO기능 50% 성능 개선과 같은 정량적 목표를 부여한다면 달성했을때 더 큰 보람을 스스로 느낄 것이다 

콩쥐야.. 조때써

 

“xxx-api CPU가 계속 스파이크를 찍네요, 이유가 무엇인가요?”

 

위 질문에 답변을 못한다는 것을 장애가 터지고 깨달았다, 사실 인지는 했지만 외면해두고 있었다

최근 배포한 기능들을 뒤져보며 문제가 될 만한 부분을 찾아 급한 불은 우선 껐지만, 현 구조가 장애 대응 능력이 부족하다는 것이 느껴졌다.

또한 사용자와 서비스 규모가 늘어나면서, 장애 상황에서의 빠른 대응이 팀의 우선순위가 되었다.

“무엇이 원인인지“ 더욱 빠르게 파악할수 있는 해결책이 필요하다.


 

현재 우리의 서버는 아래 로깅을 지원하고 있다

  • 서버 예외 sentry 로그
  • k8s pod 시스템 성능 지표 (CPU, MEM, Net I/O, …)
  • db 시스템 성능 지표 (CPU, MEM, Net I/O, …)
  • nginx http 로그

시스템 레벨의 지표는 수집하고 있지만, 애플리케이션 레벨의 관찰 가능성이 부족하여 “어떤 코드에서 발생했는지" 파악할 수 없는 상황이다.

따라서 다음과 같은 요소들이 추가로 필요하다:

Metric

어플리케이션의 상태 트렌드를 파악하기 위해 필요하다.

  • API 엔드포인트별 응답시간, 처리량(TPS), 에러율
  • 비즈니스 로직별 실행시간 (예: 결제 처리, 데이터 조회 등)

Logging

metric에서 트렌드를 파악하고, 구체적으로 어떤 문제인지 파악하기 위해 필요하다

  • nginx http 로그
  • 애플리케이션 에러 로그 (stack trace, 예외 상황)

Trace

하나의 요청이 여러 서비스를 거쳐가는 과정을 추적하여, 어느 구간에서 지연이나 문제가 발생했는지 파악하기 위해 필요하다

  • 각 API 호출의 상세 실행 과정 로깅
  • 마이크로서비스 간 호출 관계와 소요시간

Alert

문제 발생 시 즉시 인지하고 빠른 대응을 위해 필요하다

  • 임계값 기반 실시간 알림 (응답시간 >3초, 에러율 >5% 등)
  • 일정 시간 내 미응답 시 상위 담당자에게 자동 확대 알림

현재 logging은 loki 기반으로 nginx http 로그를 수집하고 있어 어느정도 갖춰져 있는 상태이다.

우선 어플리케이션 레벨 metric을 수집하는 것에 초점을 맞춰보자

+ Recent posts