본문으로 건너뛰기

성능 모니터링 시스템

애플리케이션의 성능 지표를 실시간으로 수집하고 분석하는 종합적인 모니터링 시스템입니다.

🏗️ 아키텍처 개요

Application → @Measure 데코레이터 → MetricsPublisherService → Pub/Sub (performance-metrics) → BigQuery → 분석 대시보드

주요 구성 요소

  1. @Measure 데코레이터: 메서드 실행 시간과 메모리 사용량 자동 측정
  2. MetricsPublisherService: GCP Pub/Sub로 메트릭 데이터 발행
  3. Pub/Sub Topic: performance-metrics 토픽으로 실시간 메트릭 스트리밍
  4. BigQuery: 메트릭 데이터 저장 및 분석
  5. 분석 쿼리: 성능 트렌드 및 이상 패턴 감지

🚀 사용법

1. 데코레이터 적용

서비스 메서드에 @Measure 데코레이터를 추가하여 성능 측정을 활성화합니다.

import { Injectable } from '@nestjs/common';
import { Measure } from '@shared/utils';

@Injectable()
export class SleepLogService {

// 항상 메트릭 수집
@Measure({
module: 'SleepLogService',
collectMetrics: true
})
async createSleepLog(userId: string): Promise<SleepLogResponseDto> {
// 메서드 구현
}

// 임계값 초과 시에만 메트릭 수집
@Measure({
module: 'SleepLogService',
thresholdMs: 500
})
async getSleepLogByDayIndex(userId: string): Promise<SleepLogResponseDto> {
// 메서드 구현
}

// 항상 수집 + 임계값 모니터링 (collectMetrics 우선)
@Measure({
module: 'SleepLogService',
collectMetrics: true,
thresholdMs: 2000
})
async getSleepLogRange(userId: string): Promise<SleepLogRangeResponseDto> {
// 메서드 구현
}
}

2. 데코레이터 옵션

옵션타입기본값설명
modulestring필수모듈명 (분석 시 그룹핑에 사용)
thresholdMsnumber-임계값 초과 시에만 메트릭 수집 (밀리초)
collectMetricsbooleanfalse항상 메트릭 수집 여부

3. 메트릭 서비스 주입

import { MetricsPublisherService } from '@core/metrics';

@Injectable()
export class YourService {
constructor(
private readonly metricsPublisher: MetricsPublisherService
) {}

// 데코레이터가 자동으로 metricsPublisher를 사용
}

📊 데이터 스키마

BigQuery 테이블: log.performance_metrics

필드타입설명
timestampTIMESTAMP메트릭 수집 시간
service_nameSTRING서비스명 (예: dta-wide-api)
environmentSTRING환경 (dev/staging/production)
moduleSTRING모듈명 (예: SleepLogService)
methodSTRING메서드명 (예: createSleepLog)
duration_msFLOAT64실행 시간 (밀리초)
heap_used_mbFLOAT64사용된 힙 메모리 (MB)
heap_total_mbFLOAT64전체 힙 메모리 (MB)
successBOOLEAN실행 성공 여부
error_messageSTRING에러 메시지 (실패 시)
user_idSTRING사용자 ID (옵션)
correlation_idSTRING요청 추적 ID
request_pathSTRINGAPI 요청 경로 (해당하는 경우)

📈 성능 분석 쿼리

1. 일별 성능 트렌드

SELECT
DATE(timestamp) as date,
environment,
module,
AVG(duration_ms) as avg_response_time,
PERCENTILE_CONT(duration_ms, 0.95) OVER (
PARTITION BY DATE(timestamp), environment, module
) as p95_response_time,
COUNTIF(success = false) as error_count,
COUNT(*) as total_requests
FROM `{PROJECT_ID}.log.performance_metrics`
WHERE timestamp >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 30 DAY)
GROUP BY DATE(timestamp), environment, module
ORDER BY date DESC, environment, module;

2. 가장 느린 메서드 TOP 20

SELECT
environment,
module,
method,
COUNT(*) as execution_count,
AVG(duration_ms) as avg_duration_ms,
PERCENTILE_CONT(duration_ms, 0.95) OVER (
PARTITION BY environment, module, method
) as p95_duration_ms,
MAX(duration_ms) as max_duration_ms,
COUNTIF(success = false) as error_count
FROM `{PROJECT_ID}.log.performance_metrics`
WHERE timestamp >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 7 DAY)
GROUP BY environment, module, method
HAVING COUNT(*) >= 10
ORDER BY avg_duration_ms DESC
LIMIT 20;

3. 실시간 모니터링 (지난 1시간)

SELECT
environment,
service_name,
COUNT(*) as requests_last_hour,
AVG(duration_ms) as avg_response_time,
PERCENTILE_CONT(duration_ms, 0.95) OVER (
PARTITION BY environment, service_name
) as p95_response_time,
COUNTIF(success = false) as errors_last_hour,
SAFE_DIVIDE(COUNTIF(success = false), COUNT(*)) * 100 as error_rate,
AVG(heap_used_mb) as avg_memory_usage
FROM `{PROJECT_ID}.log.performance_metrics`
WHERE timestamp >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 1 HOUR)
GROUP BY environment, service_name
ORDER BY error_rate DESC, avg_response_time DESC;

4. 메모리 사용량 분석

SELECT
environment,
module,
method,
AVG(heap_used_mb) as avg_heap_used,
MAX(heap_used_mb) as max_heap_used,
AVG(heap_total_mb) as avg_heap_total,
AVG(duration_ms) as avg_duration,
COUNT(*) as sample_count
FROM `{PROJECT_ID}.log.performance_metrics`
WHERE
timestamp >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 24 HOUR)
AND heap_used_mb IS NOT NULL
GROUP BY environment, module, method
HAVING COUNT(*) >= 5
ORDER BY max_heap_used DESC
LIMIT 50;

🎯 성능 최적화 가이드

1. 임계값 설정 가이드라인

작업 유형권장 임계값설명
단순 조회500ms데이터베이스 단순 조회
복잡 조회1000ms조인이나 집계가 포함된 조회
생성/수정1000ms데이터 생성/수정 작업
대량 처리2000ms배치 처리나 대량 데이터 작업
외부 API3000ms외부 서비스 호출

2. 모니터링 베스트 프랙티스

2.1 메트릭 수집 전략

// ✅ 좋은 예: 핵심 비즈니스 로직은 항상 수집
@Measure({
module: 'PaymentService',
collectMetrics: true
})
async processPayment() { }

// ✅ 좋은 예: 일반적인 조회는 임계값 기반 수집
@Measure({
module: 'UserService',
thresholdMs: 500
})
async searchUsers() { }

// ✅ 좋은 예: 복합 전략 (항상 수집 + 임계값 모니터링)
@Measure({
module: 'DataService',
collectMetrics: true,
thresholdMs: 2000 // 2초 초과 시 성능 이슈로 간주
})
async processLargeData() { }

2.2 적절한 모듈명 사용

// ✅ 좋은 예: 명확하고 구체적인 모듈명
@Measure({ module: 'SleepLogService' })
@Measure({ module: 'UserAuthenticationService' })
@Measure({ module: 'PaymentProcessingService' })

// ❌ 나쁜 예: 모호하거나 일반적인 모듈명
@Measure({ module: 'Service' })
@Measure({ module: 'Handler' })
@Measure({ module: 'Utils' })

2.3 임계값 설정 전략

// ✅ 좋은 예: 작업 특성에 맞는 임계값
@Measure({
module: 'DatabaseService',
thresholdMs: 500 // DB 조회는 500ms 초과 시 모니터링
})
async findUser() { }

@Measure({
module: 'ExternalApiService',
thresholdMs: 3000 // 외부 API는 3초 초과 시 모니터링
})
async callExternalApi() { }

// ✅ 좋은 예: 중요한 작업은 항상 수집
@Measure({
module: 'CriticalService',
collectMetrics: true // 임계값과 관계없이 모든 실행 수집
})
async criticalOperation() { }

🚨 알림 및 모니터링

1. 성능 기준 위반 감지

-- SLA 위반 (1초 이상 소요되는 작업) 감지
SELECT
environment,
module,
method,
COUNT(*) as violations,
AVG(duration_ms) as avg_slow_duration,
MAX(duration_ms) as max_duration
FROM `{PROJECT_ID}.log.performance_metrics`
WHERE
timestamp >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 1 HOUR)
AND duration_ms > 1000
GROUP BY environment, module, method
HAVING COUNT(*) >= 5
ORDER BY violations DESC;

2. 에러율 급증 감지

-- 지난 1시간 에러율이 5% 이상인 메서드
SELECT
environment,
module,
method,
COUNT(*) as total_requests,
COUNTIF(success = false) as error_count,
SAFE_DIVIDE(COUNTIF(success = false), COUNT(*)) * 100 as error_rate
FROM `{PROJECT_ID}.log.performance_metrics`
WHERE timestamp >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 1 HOUR)
GROUP BY environment, module, method
HAVING
COUNT(*) >= 10
AND error_rate >= 5.0
ORDER BY error_rate DESC;

3. 메모리 누수 감지

-- 시간에 따른 메모리 사용량 증가 추세 감지
WITH hourly_memory AS (
SELECT
DATETIME_TRUNC(DATETIME(timestamp), HOUR) as hour,
environment,
module,
AVG(heap_used_mb) as avg_heap_used
FROM `{PROJECT_ID}.log.performance_metrics`
WHERE
timestamp >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 24 HOUR)
AND heap_used_mb IS NOT NULL
GROUP BY hour, environment, module
),
memory_trend AS (
SELECT
*,
LAG(avg_heap_used, 1) OVER (
PARTITION BY environment, module
ORDER BY hour
) as prev_heap_used
FROM hourly_memory
)
SELECT
environment,
module,
hour,
avg_heap_used,
prev_heap_used,
(avg_heap_used - prev_heap_used) as memory_increase
FROM memory_trend
WHERE
prev_heap_used IS NOT NULL
AND (avg_heap_used - prev_heap_used) > 50 -- 50MB 이상 증가
ORDER BY environment, module, hour DESC;

🔧 설정 및 배포

1. 환경 설정

// app.module.ts
import { CoreMetricsModule } from '@core/metrics';

@Module({
imports: [
CoreMetricsModule, // 메트릭 서비스 등록
// ... 기타 모듈
]
})
export class AppModule {}

2. 인프라 배포

BigQuery 테이블 배포

# dev 환경
cd infrastructure/terragrunt/dev/bigquery
terragrunt apply

# stage 환경
cd infrastructure/terragrunt/stage/bigquery
terragrunt apply

# prod 환경
cd infrastructure/terragrunt/prod/bigquery
terragrunt apply

Pub/Sub 리소스 배포

# dev 환경
cd infrastructure/terragrunt/dev/pubsub
terragrunt apply

# stage 환경
cd infrastructure/terragrunt/stage/pubsub
terragrunt apply

# prod 환경
cd infrastructure/terragrunt/prod/pubsub
terragrunt apply

3. 환경별 설정 차이

환경BigQuery 보존 기간토픽명프로젝트 ID
dev90일performance-metricsdta-cloud-de-dev
stage90일performance-metricsdta-cloud-de-stage
prod180일performance-metricsdta-cloud-de-prod

📋 문제 해결

1. 메트릭이 BigQuery에 나타나지 않는 경우

  1. Pub/Sub 토픽 확인

    gcloud pubsub topics list --filter="name:performance-metrics"
  2. 구독 상태 확인

    gcloud pubsub subscriptions list --filter="name:performance-metrics-to-bigquery"
  3. 데드레터 큐 확인

    gcloud pubsub topics list --filter="name:performance-metrics-dead-letter"
    gcloud pubsub subscriptions pull performance-metrics-dead-letter --limit=10
  4. BigQuery 테이블 확인

    SELECT COUNT(*) FROM `{PROJECT_ID}.log.performance_metrics`
    WHERE timestamp >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 1 HOUR);

2. 성능 측정이 작동하지 않는 경우

  1. 데코레이터 임포트 확인

    // ✅ 올바른 임포트
    import { Measure } from '@shared/utils';

    // ❌ 잘못된 임포트
    import { Measure } from '@nestjs/common';
  2. 메트릭 서비스 주입 확인

    constructor(
    private readonly metricsPublisher: MetricsPublisherService
    ) {}
  3. 메트릭 수집 조건 확인

    // collectMetrics: true이면 항상 수집
    @Measure({ module: 'TestService', collectMetrics: true })

    // thresholdMs만 있으면 임계값 초과 시에만 수집
    @Measure({ module: 'TestService', thresholdMs: 1000 })
  4. 콘솔 로그 확인

    # 메트릭 발행 실패 로그 확인
    kubectl logs -f deployment/dta-wide-api | grep -i "Failed to publish metric"

    # 메트릭 수집 실패 로그 확인
    kubectl logs -f deployment/dta-wide-api | grep -i "Metric collection failed"

3. 높은 메모리 사용량 경고

  1. 메모리 누수 패턴 확인
  2. 가비지 컬렉션 로그 분석
  3. 힙 덤프 생성 및 분석
  4. 메트릭 수집 최적화
    // 과도한 메트릭 수집 방지
    // ❌ 모든 메서드에 collectMetrics: true
    @Measure({ module: 'Service', collectMetrics: true })

    // ✅ 임계값 기반으로 필요한 경우만 수집
    @Measure({ module: 'Service', thresholdMs: 1000 })

🔄 향후 개선 사항

1. 실시간 대시보드

  • Grafana 대시보드: BigQuery 데이터 소스 연동
  • DataStudio 리포트: 자동 생성 성능 리포트
  • 모바일 알림: 임계값 초과 시 즉시 알림

2. 자동 알림 시스템

  • Slack 통합: 성능 기준 위반 시 채널 알림
  • 이메일 알림: 주간/월간 성능 보고서 자동 발송
  • PagerDuty 연동: 심각한 성능 이슈 시 온콜 엔지니어 호출

3. 예측 분석

  • ML 모델: BigQuery ML을 활용한 성능 이상 패턴 예측
  • 용량 계획: 트래픽 증가에 따른 리소스 확장 계획
  • 트렌드 분석: 성능 개선/악화 추세 자동 감지

4. 분산 트레이싱

  • OpenTelemetry 연동: 요청 전체 흐름 추적
  • Cloud Trace 통합: GCP 네이티브 분산 추적
  • 상관관계 분석: 요청 간 성능 영향도 분석

문서 변경이력

버전날짜작성자변경내용
0.1.02025-06-16bok@weltcorp.com최초 작성