BackEnd

APM (Application Performance Management)

sihanni 2025. 6. 22. 00:43

APM?

"애플리케이션의 성능을 실시간으로 모니터링하고 분석할 수 있는 도구"

 

APM은 말 그대로 애플리케이션이 실제로 어떻게 작동하고 있는지를 실시간으로 추적하고 시각화해서, 성능 문제를 조기에 감지하고 대응할 수 있게 도와주는 도구이다.

APM을 통해 고객이 지속적으로 긍정적인 경험을 얻도록 개선할 수 있다.

 

APM이 하는 일

  • 트랜잭션 추적
    • 요청이 어디서 지연되는지를 추적 (예: DB, 외부 API 등)
  • 메트릭(Metrics) 수집
    • CPU, 메모리, 요청 속도, DB쿼리 시간, 에러율 등 수집
  • 에러 추적
    • 예외 발생 위치, 스택 트레이스, 요청 context 제공
  • 슬로우 쿼리 감지
    • 느린 DB 쿼리 또는 API 응답 시간 추적
  • 분산 추적 (Distributed Tracing)
    • MSA 환경에서 서비스 간 흐름 추적
  • 알림
    • 성능 임계값 초과 시 Slack, 이메일 등으로 알림 전송
  • 대시보드
    • 실시간 성능 지표와 로그를 시각적으로 제공
  • 사용자 경험
    • 페이지 로딩 속도, JS 오류 등 프론트엔드 포함

대표적인 APM 도구들

APM 도구 특징  사용 상황
New Relic 매우 강력한 APM, MSA 전체 추적 가능, 요금제 비쌈 대기업, 고도화 서비스
Datadog APM 인프라 + APM 통합, 시각화 훌륭, 다양한 연동 Kubernetes, AWS 등 클라우드 서비스
Sentry 프론트/백엔드 통합 추적, 성능 추적기능도 포함 Node.js, React, Vue 등 JS 기반 서비스
Elastic APM (ELK스택) 오픈소스 기반, Kibana로 대시보드 구성 자체 구죽 가능한 환경
Jeager CNCF 분산 트레이싱 시스템 MSA 환경, 비용 없이 분산 추적 원할 때
Zipkin Twitter 오픈 소스, Jaeger과 유사 가볍게 추적용 분산 트레이싱
OpenTelemetry 벤더 독립 표준, 다양한 APM으로 전환 가능 NewRelic, Datadog, Prometheus 등과 연동 가능

이중에서 자주 듣고 접했던 건 Sentry, Datadog이고 직접 사용해본건 ELK 스택, Prometheus + Grafana 조합 정도 인 것 같다.

이번 글에서는 Sentry 에 대해 알아보고자 한다.

 

Sentry

sentry?

https://sentry.io/welcome/?utm_source=google&utm_medium=cpc&utm_id={21427619193}&utm_campaign=Google_Search_Brand_SentryKW_APAC_Alpha&utm_content=g&utm_term=sentry&gad_source=1&gad_campaignid=21427619193&gbraid=0AAAAADua1WIt9ST8m04byrZkdZZ9FfMgi&gclid=Cj0KCQjwsNnCBhDRARIsAEzia4Cdz7UXudd0ucoZitgZySiH1cmBv4qoWLT1YlPDzsoHVZ2kRIPeTz4aApObEALw_wcB

 

Application Performance Monitoring & Error Tracking Software

Application performance monitoring for developers & software teams to see errors clearer, solve issues faster & continue learning continuously.

sentry.io

  • Install
npm install @sentry/node @sentry/tracing

 

  • 커스텀 에러 스크립트 생성
// DB 관련 에러 커스텀 클래스
import { Logger as TypeOrmLogger, QueryRunner } from 'typeorm';
import * as Sentry from '@sentry/node';

export class SimpleTypeOrmSentryLogger implements TypeOrmLogger {
  private readonly slowQueryThreshold = 300; // ms 기준

  logQuery(query: string, parameters?: any[], queryRunner?: QueryRunner) {
    const start = Date.now();
    console.log('[QUERY]', query);
    if (queryRunner) {
      queryRunner.data = {
        ...(queryRunner.data || {}),
        __sentryQueryStart: start,
        __sentryQuery: query,
        __sentryParams: parameters,
      };
    }
  }

  logQueryError(error: string | Error, query: string, parameters?: any[]) {
    const err = error instanceof Error ? error : new Error(error);
    Sentry.captureException(err, {
      extra: {
        query,
        parameters,
      },
    });
  }

  logQuerySlow(time: number, query: string, parameters?: any[]) {
    console.log('[SLOW QUERY]', query);
    if (time > this.slowQueryThreshold) {
      Sentry.captureMessage(
        `[SLOW QUERY] ${time}ms\n${query}\nParams: ${JSON.stringify(parameters)}`,
        'warning',
      );
    }
  }

  log(level: 'log' | 'info' | 'warn', message: any) {
    if (level === 'warn') {
      Sentry.captureMessage(`[TypeORM Warn] ${message}`, 'warning');
    }
  }

  logMigration(message: string) {
    console.log('[Migration]', message);
  }

  logSchemaBuild(message: string) {
    console.log('[SchemaBuild]', message);
  }
}

 

// API 에러 커스텀 클래스
import {
  ArgumentsHost,
  Catch,
  ExceptionFilter,
  HttpException,
  HttpStatus,
} from '@nestjs/common';
import { HttpAdapterHost } from '@nestjs/core';
import * as Sentry from '@sentry/node';

@Catch()
export class ExceptionsFilter implements ExceptionFilter {
  constructor(private readonly httpAdapterHost: HttpAdapterHost) {}

  catch(exception: any, host: ArgumentsHost) {
    Sentry.captureException(exception);
    // In certain situations `httpAdapter` might not be available in the
    // constructor method, thus we should resolve it here.
    console.log('in exception filter');
    const { httpAdapter } = this.httpAdapterHost;
    const ctx = host.switchToHttp();

    const httpStatus =
      exception instanceof HttpException
        ? exception.getStatus()
        : HttpStatus.INTERNAL_SERVER_ERROR;

    const responseBody = {
      statusCode: httpStatus,
      error: exception instanceof Error ? exception.message : exception,
      message:
        exception instanceof Error
          ? exception.message
          : 'Internal Server Error',
      timestamp: new Date().toISOString(),
      path: httpAdapter.getRequestUrl(ctx.getRequest()),
    };

    httpAdapter.reply(ctx.getResponse(), responseBody, httpStatus);
  }
}

 

위 처럼 커스텀 클래스를 생성해서 API 에러나 슬로우 쿼리, 쿼리 에러로그를 테스트 해보았다.

 

정리

APM이 어떤것인지, 그리고 전반적으로 가장 많이 사용되는 Sentry를 통해 간단한 테스트를 진행해보았다.

 

실제 서비스에서 추가하면 좋을 것들

  • 트랜잭션 데이터
    • API 요청별 처리 시간 및 경로 추적
    • 각 서비스 또는 마이크로 서비스 호출 간의 연쇄호출
    • 복잡한 비즈니스 로직의 경우 트랜잭션 시작부터 끝까지 지연 시간 병목 구간 파악
  • 에러 및 예외
    • 발생환 예외 및 오류 로그, 에러 빈도 수집
  • 메트릭 (Metrics)
    • 응답 시간, 처리량, 네트워크 I/O
    • DB 쿼리 실행 시간 및 빈도
  • 로그
    • 애플리케이션로그 (info, warn, error)
    • 컨텍스트가 포함된 구조화 로그
    • 요청 및 응답 로그
  • 인프라 및 시스템 상세정보
    • 서버 및 컨테이너 상태 (CPU, 메모리, 디스크, 네트워크)
    • 서비스 상태 및 가용성
    • 오케스트레이션 상태 (ex: 쿠버네티스 pod 상태)