FastAPI에서 Sentry APM 비용 절감: 중요한 것만 전송하기

발행: (2026년 1월 8일 오후 03:41 GMT+9)
7 min read
원문: Dev.to

Source: Dev.to

번역을 진행하려면 번역하고자 하는 전체 텍스트(마크다운 형식 포함)를 제공해 주시겠어요? 코드 블록과 URL은 그대로 유지하고, 나머지 내용을 한국어로 번역해 드리겠습니다.

기본 APM의 실제 문제

기본적으로, Sentry APM은 매우 관대합니다:

  • 모든 요청이 트랜잭션이 됩니다
  • 서브초 수준의 성공적인 호출도 기록됩니다
  • 문서 및 스키마 엔드포인트도 추적됩니다

트래픽이 많은 API에서는 이것이 빠르게 다음과 같이 변합니다:

  • 대량의 트랜잭션 양
  • 더 빠른 할당량 소진
  • 인사이트 대신 잡음에 비용을 지불하게 됨

실제로 저는 다음에만 가시성이 필요했습니다:

  • 실패하는 요청(5xx)
  • 느린 요청
  • 비정상적이거나 위험한 모든 것

그 외의 모든 것은 배경 잡음에 불과했습니다.

비용 절감 전략

항상 Sentry에 전송

  • 5xx를 반환하는 모든 요청
  • 5초 이상 걸리는 모든 요청

Sentry에서 제외

  • 빠른 GET / POST / PUT 요청
  • 3초 이하에 완료되는 성공적인 요청
  • /docs/openapi.json 엔드포인트

이는 Sentry가 트래픽 양이 아니라 문제에 집중하도록 합니다.

왜 두 개의 미들웨어가 필요한가

SentryAsgiMiddleware – APM 활성화

SentryAsgiMiddleware는 실제로:

  • Sentry 트랜잭션을 시작하고 종료합니다
  • ASGI 요청 라이프사이클에 연결합니다
  • 성능 데이터를 Sentry에 전송합니다

이 미들웨어가 없으면:

  • 트랜잭션이 생성되지 않습니다
  • before_send_transaction이 호출되지 않습니다
  • APM이 작동하지 않습니다

요약: SentryAsgiMiddleware가 없으면 APM도 없습니다

TimingMiddleware – 인텔리전스 추가

두 번째 미들웨어는 커스텀입니다. 각 요청의 실제 실행 시간을 측정하고 이를 Sentry 스코프에 첨부합니다.

class TimingMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, request: Request, call_next):
        start_time = time.time()
        response = await call_next(request)
        duration = time.time() - start_time

        with sentry_sdk.configure_scope() as scope:
            scope.set_extra("duration", duration)

        return response

필요한 이유:

  • 실행 시간은 요청이 “중요한지” 판단하는 데 필요합니다
  • Sentry 내부 타이밍은 필터링에 쉽게 활용할 수 없습니다
  • 이것이 없으면 비용 제어 로직이 추측에 의존하게 됩니다

이렇게 생각해 보세요:

  • SentryAsgiMiddleware는 파이프라인입니다
  • TimingMiddleware는 두뇌입니다

전송 전 트랜잭션 필터링

Sentry는 before_send_transaction이라는 훅을 제공합니다. 이 훅은 트랜잭션이 Sentry에 전송되기 직전에 실행되며, 트랜잭션을 삭제할 수 있게 해줍니다.

def before_send_transaction(event, hint):
    transaction_name = event.get("transaction", "")
    request_method = event.get("request", {}).get("method", "")
    status_code = event.get("contexts", {}).get("response", {}).get("status_code", 0)

    duration = event.get("extra", {}).get("duration")

    # Ignore docs and schema
    if "/docs" in transaction_name or "/openapi.json" in transaction_name:
        return None

    # Always send server errors
    if status_code >= 500:
        return event

    # Drop fast successful requests
    if (
        request_method in ["GET", "POST", "PUT"]
        and 200 <= status_code < 400
        and duration
        and duration < 3
    ):
        return None

    return event
  • event 반환 → 트랜잭션이 전송됩니다
  • None 반환 → 트랜잭션이 삭제됩니다

간단하고 예측 가능하며 완전히 여러분의 제어 하에 있습니다.

사용자 지정 필터링을 통한 Sentry 초기화

sentry_sdk.init(
    dsn="SENTRY_DSN",
    send_default_pii=True,
    traces_sample_rate=1.0,
    before_send_transaction=before_send_transaction,
)

무작위 샘플링에 의존하는 대신, 이 접근 방식은 실제 동작을 기반으로 한 결정론적 필터링을 제공합니다.

무엇이 바뀌었나요

비용 절감

거래량이 급격히 감소했고, Sentry 사용량도 즉시 감소했습니다.

더 깔끔한 대시보드

느리거나 실패한 요청만 표시되어 디버깅이 쉬워졌습니다.

더 나은 신호

Sentry의 모든 트랜잭션이 이제 “이것은 살펴볼 가치가 있다.” 를 의미합니다.

When This Approach Makes Sense

  • API 트래픽이 많음
  • 대부분의 요청이 성공적이고 빠름
  • 원시 메트릭보다 이슈에 더 신경씀

모든 요청을 영원히 추적하고 싶다면 이 접근 방식은 적합하지 않습니다. 비용을 많이 들이지 않고 유용한 가시성을 원한다면 바로 이 방법입니다.

최종 생각

APM은 청구 대시보드에서 새로운 문제를 만들지 않고, 문제를 찾는 데 도움이 되어야 합니다.

다음 요소들을 결합함으로써:

  • SentryAsgiMiddleware
  • 간단한 타이밍 미들웨어
  • before_send_transaction

Sentry를 **“모두 수집”**에서 **“실제로 중요한 것만 수집”**으로 바꿉니다. 이 작은 변화가 실제 운영 시스템에서 큰 차이를 만들습니다.

Back to Blog

관련 글

더 보기 »

Logger 모듈을 사용한 FastAPI 기본 로깅

Application이 Production에서 충돌할 때 Production에서 애플리케이션이 충돌했으며 사용자는 다음에 무엇을 해야 할지 확신하지 못했습니다. 나는 작년에 이와 같은 상황을 프로젝트에서 경험했습니다.