성능 모니터링 시스템
애플리케이션의 성능 지표를 실시간으로 수집하고 분석하는 종합적인 모니터링 시스템입니다.
🏗️ 아키텍처 개요
Application → @Measure 데코레이터 → MetricsPublisherService → Pub/Sub (performance-metrics) → BigQuery → 분석 대시보드
주요 구성 요소
- @Measure 데코레이터: 메서드 실행 시간과 메모리 사용량 자동 측정
- MetricsPublisherService: GCP Pub/Sub로 메트릭 데이터 발행
- Pub/Sub Topic:
performance-metrics토픽으로 실시간 메트릭 스트리밍 - BigQuery: 메트릭 데이터 저장 및 분석
- 분석 쿼리: 성능 트렌드 및 이상 패턴 감지
🚀 사용법
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. 데코레이터 옵션
| 옵션 | 타입 | 기본값 | 설명 |
|---|---|---|---|
module | string | 필수 | 모듈명 (분석 시 그룹핑에 사용) |
thresholdMs | number | - | 임계값 초과 시에만 메트릭 수집 (밀리초) |
collectMetrics | boolean | false | 항상 메트릭 수집 여부 |
3. 메트릭 서비스 주입
import { MetricsPublisherService } from '@core/metrics';
@Injectable()
export class YourService {
constructor(
private readonly metricsPublisher: MetricsPublisherService
) {}
// 데코레이터가 자동으로 metricsPublisher를 사용
}
📊 데이터 스키마
BigQuery 테이블: log.performance_metrics
| 필드 | 타입 | 설명 |
|---|---|---|
timestamp | TIMESTAMP | 메트릭 수집 시간 |
service_name | STRING | 서비스명 (예: dta-wide-api) |
environment | STRING | 환경 (dev/staging/production) |
module | STRING | 모듈명 (예: SleepLogService) |
method | STRING | 메서드명 (예: createSleepLog) |
duration_ms | FLOAT64 | 실행 시간 (밀리초) |
heap_used_mb | FLOAT64 | 사용된 힙 메모리 (MB) |
heap_total_mb | FLOAT64 | 전체 힙 메모리 (MB) |
success | BOOLEAN | 실행 성공 여부 |
error_message | STRING | 에러 메시지 (실패 시) |
user_id | STRING | 사용자 ID (옵션) |
correlation_id | STRING | 요청 추적 ID |
request_path | STRING | API 요청 경로 (해당하는 경우) |
📈 성능 분석 쿼리
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 | 배치 처리나 대량 데이터 작업 |
| 외부 API | 3000ms | 외부 서비스 호출 |
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 |
|---|---|---|---|
| dev | 90일 | performance-metrics | dta-cloud-de-dev |
| stage | 90일 | performance-metrics | dta-cloud-de-stage |
| prod | 180일 | performance-metrics | dta-cloud-de-prod |
📋 문제 해결
1. 메트릭이 BigQuery에 나타나지 않는 경우
-
Pub/Sub 토픽 확인
gcloud pubsub topics list --filter="name:performance-metrics" -
구독 상태 확인
gcloud pubsub subscriptions list --filter="name:performance-metrics-to-bigquery" -
데드레터 큐 확인
gcloud pubsub topics list --filter="name:performance-metrics-dead-letter"
gcloud pubsub subscriptions pull performance-metrics-dead-letter --limit=10 -
BigQuery 테이블 확인
SELECT COUNT(*) FROM `{PROJECT_ID}.log.performance_metrics`
WHERE timestamp >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 1 HOUR);
2. 성능 측정이 작동하지 않는 경우
-
데코레이터 임포트 확인
// ✅ 올바른 임포트
import { Measure } from '@shared/utils';
// ❌ 잘못된 임포트
import { Measure } from '@nestjs/common'; -
메트릭 서비스 주입 확인
constructor(
private readonly metricsPublisher: MetricsPublisherService
) {} -
메트릭 수집 조건 확인
// collectMetrics: true이면 항상 수집
@Measure({ module: 'TestService', collectMetrics: true })
// thresholdMs만 있으면 임계값 초과 시에만 수집
@Measure({ module: 'TestService', thresholdMs: 1000 }) -
콘솔 로그 확인
# 메트릭 발행 실패 로그 확인
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. 높은 메모리 사용량 경고
- 메모리 누수 패턴 확인
- 가비지 컬렉션 로그 분석
- 힙 덤프 생성 및 분석
- 메트릭 수집 최적화
// 과도한 메트릭 수집 방지
// ❌ 모든 메서드에 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.0 | 2025-06-16 | bok@weltcorp.com | 최초 작성 |