312756adc8
Task-Url:
API Gateway 통계 수집 모듈
분단위로 API 거래 통계를 수집하고 DB에 저장하는 모듈입니다.
아키텍처
거래 발생
↓
TransactionEvent 생성
↓
ApiStatsCollector.recordEvent() ← 거래마다 호출
↓
MinuteStatsAggregator (메모리 집계)
↓
ApiStatsScheduler (매 분 5초 실행)
↓
DB 저장 (API_STATS_MINUTE 테이블)
패키지 구조
com.eactive.eai.custom.statistics/
├── dto/
│ ├── TransactionEvent.java - 거래 이벤트 DTO
│ └── MinuteStatsKey.java - 집계 키 (시간 + 차원)
├── collector/
│ ├── MinuteStatsAggregator.java - HdrHistogram 기반 집계
│ └── ApiStatsCollector.java - 메인 수집기 (ConcurrentHashMap)
├── scheduler/
│ └── ApiStatsScheduler.java - 스케줄러 (매 분 실행)
├── ApiStatsMinuteRepository.java - JPA Repository
└── ApiStatsMinuteLogger.java - DB 저장 Logger
사용법
1. 거래 처리 시 통계 수집
거래가 발생할 때마다 ApiStatsCollector.recordEvent() 호출:
@Service
public class ApiGatewayService {
@Autowired
private ApiStatsCollector statsCollector;
public void processTransaction(ApiRequest request) {
long startTime = System.currentTimeMillis();
try {
// 거래 처리
ApiResponse response = executeApi(request);
// 통계 수집 (성공)
recordStats(request, startTime, true, null);
return response;
} catch (TimeoutException e) {
// 통계 수집 (Timeout)
recordStats(request, startTime, false, ErrorType.TIMEOUT);
throw e;
} catch (SystemException e) {
// 통계 수집 (System Error)
recordStats(request, startTime, false, ErrorType.SYSTEM_ERROR);
throw e;
} catch (BusinessException e) {
// 통계 수집 (Business Error)
recordStats(request, startTime, false, ErrorType.BUSINESS_ERROR);
throw e;
}
}
private void recordStats(ApiRequest request, long startTime,
boolean success, ErrorType errorType) {
long responseTime = System.currentTimeMillis() - startTime;
TransactionEvent event = TransactionEvent.builder()
.apiName(request.getApiName())
.gwInstanceId(getInstanceId())
.bizDivCode(TransactionEvent.normalizeValue(request.getBizDivCode()))
.clientId(TransactionEvent.normalizeValue(request.getClientId()))
.inboundAdapter(TransactionEvent.normalizeValue(request.getInboundAdapter()))
.outboundAdapter(TransactionEvent.normalizeValue(request.getOutboundAdapter()))
.responseTimeMs(responseTime)
.success(success)
.errorType(errorType)
.build();
statsCollector.recordEvent(event);
}
}
2. 스케줄러 자동 실행
ApiStatsScheduler가 자동으로 매 분 5초에 실행되어 DB에 저장합니다.
매 분 5초 (예: 10:00:05, 10:01:05, ...)
↓
완료된 분의 통계를 flush (예: 10:00:00 분 데이터)
↓
DB 저장
3. 스케줄러 활성화
Spring Boot 애플리케이션에 @EnableScheduling 추가:
@SpringBootApplication
@EnableScheduling // ← 스케줄러 활성화
public class EapimOnlineApplication {
public static void main(String[] args) {
SpringApplication.run(EapimOnlineApplication.class, args);
}
}
주요 클래스 설명
TransactionEvent
거래 1건의 정보를 담는 DTO:
- API명, Instance ID, 업무구분코드, Client ID
- Inbound/Outbound Adapter
- 응답시간, 성공 여부, 오류 유형
MinuteStatsAggregator
분단위 집계를 수행:
- HdrHistogram: P50, P95 백분위 계산
- AtomicLong: 건수 카운터 (Thread-safe)
- snapshot(): 집계 결과를 Entity로 변환
ApiStatsCollector
메모리에서 통계 수집:
- ConcurrentHashMap: 멀티스레드 안전
- computeIfAbsent(): Lock-free 집계
- flushCompletedMinutes(): 완료된 분의 데이터 반환
ApiStatsScheduler
매 분마다 DB 저장:
- @Scheduled(cron = "5 * * * * ?"): 매 분 5초 실행
- @PreDestroy: 종료 시 남은 데이터 저장
성능 특징
메모리 효율
- HdrHistogram: 수만 건을 2~4KB로 압축
- ConcurrentHashMap: 차원별로 독립적인 집계
- 분 단위 flush: 메모리 누적 방지
Thread-safe
- ConcurrentHashMap: Lock-free read
- AtomicLong: Lock-free increment
- synchronized: HdrHistogram 기록 시만 사용
성능 영향 최소화
- 거래당 오버헤드: < 1ms (메모리 연산만)
- DB 저장: 비동기 (스케줄러)
- Lock contention: 최소화
설정
PropManager를 통한 동적 설정 (권장)
운영 중에 변경 가능한 설정값을 PropManager로 관리합니다.
1. DB에 설정값 등록
PropManager 그룹명: ApiStats
| 키 | 설명 | 기본값 | 예시 |
|---|---|---|---|
enabled |
통계 수집 활성화 여부 | true | true: 활성화, false: 비활성화 |
histogram.min.ms |
히스토그램 최소값 (ms) | 1 | 응답시간 최소 기록값 |
histogram.max.ms |
히스토그램 최대값 (ms) | 60000 | 60초 초과 시 클리핑 |
histogram.precision |
히스토그램 정밀도 (유효숫자) | 3 | 3자리 정밀도 |
DB 설정 예시:
-- TSEAIRM24 테이블에 설정값 추가
INSERT INTO TSEAIRM24 (GROUP_NAME, PROP_KEY, PROP_VALUE, DESCRIPTION)
VALUES ('ApiStats', 'enabled', 'true', '통계 수집 활성화 여부');
INSERT INTO TSEAIRM24 (GROUP_NAME, PROP_KEY, PROP_VALUE, DESCRIPTION)
VALUES ('ApiStats', 'histogram.min.ms', '1', '히스토그램 최소값(ms)');
INSERT INTO TSEAIRM24 (GROUP_NAME, PROP_KEY, PROP_VALUE, DESCRIPTION)
VALUES ('ApiStats', 'histogram.max.ms', '60000', '히스토그램 최대값(ms)');
INSERT INTO TSEAIRM24 (GROUP_NAME, PROP_KEY, PROP_VALUE, DESCRIPTION)
VALUES ('ApiStats', 'histogram.precision', '3', '히스토그램 정밀도');
2. 설정 변경 방법
통계 수집 OFF (긴급 상황 시)
-- 통계 수집 비활성화
UPDATE TSEAIRM24
SET PROP_VALUE = 'false'
WHERE GROUP_NAME = 'ApiStats' AND PROP_KEY = 'enabled';
// PropManager 리로드 (즉시 적용)
PropManager.getInstance().reload("ApiStats");
통계 수집 ON (다시 활성화)
-- 통계 수집 재활성화
UPDATE TSEAIRM24
SET PROP_VALUE = 'true'
WHERE GROUP_NAME = 'ApiStats' AND PROP_KEY = 'enabled';
// PropManager 리로드
PropManager.getInstance().reload("ApiStats");
히스토그램 범위 변경
-- 최대값을 300초로 변경
UPDATE TSEAIRM24
SET PROP_VALUE = '300000'
WHERE GROUP_NAME = 'ApiStats' AND PROP_KEY = 'histogram.max.ms';
// PropManager 리로드
PropManager.getInstance().reload("ApiStats");
방법 2: 관리 화면을 통한 수정
- EMS 관리 화면에서 PropManager 설정 수정
- 저장 시 자동으로 메모리에 반영
3. 설정값 확인
애플리케이션 시작 시 로그에서 확인:
ApiStatsScheduler initialized
ApiStatsConfig] enabled = true
ApiStatsConfig] histogram.min.ms = 1
ApiStatsConfig] histogram.max.ms = 60000
ApiStatsConfig] histogram.precision = 3
비활성화 시 로그:
ApiStatsConfig] enabled = false
API stats collection is disabled, skipping save
스케줄러 주기 변경
ApiStatsScheduler.java의 cron 표현식 수정:
@Scheduled(cron = "5 * * * * ?") // 매 분 5초
@Scheduled(cron = "0 * * * * ?") // 매 분 0초
@Scheduled(cron = "0 */5 * * * ?") // 5분마다
주의: 스케줄러 주기는 코드 변경 후 재배포 필요
모니터링
활성 집계기 개수 확인
int count = statsCollector.getActiveAggregatorCount();
log.info("Active aggregators: {}", count);
로그 레벨 조정
# application.properties
logging.level.com.eactive.eai.custom.statistics=DEBUG
주의사항
- NULL 값 처리: 차원 값이 NULL이면 "NONE"으로 변환 필수
- 응답시간 범위: PropManager 설정값 범위 초과 시 클리핑됨 (기본: 1ms ~ 60초)
- 스케줄러 활성화:
@EnableScheduling필수 - DB 저장 실패: 로그로 기록되며 다음 분에 재시도하지 않음
- PropManager 설정:
ApiStats그룹이 DB에 없으면 기본값 사용
테스트
단위 테스트 예제
@Test
public void testStatsCollection() {
ApiStatsCollector collector = new ApiStatsCollector();
// 거래 이벤트 생성
TransactionEvent event = TransactionEvent.builder()
.apiName("TEST_API")
.gwInstanceId("GW01")
.bizDivCode("NONE")
.clientId("CLIENT01")
.inboundAdapter("HTTP")
.outboundAdapter("REST")
.responseTimeMs(100)
.success(true)
.build();
// 이벤트 기록
LocalDateTime now = LocalDateTime.of(2026, 1, 8, 10, 0, 30);
collector.recordEvent(event, now);
// Flush (10시 1분 이전)
LocalDateTime flushTime = LocalDateTime.of(2026, 1, 8, 10, 1, 0);
List<ApiStatsMinute> result = collector.flushBefore(flushTime);
// 검증
assertEquals(1, result.size());
ApiStatsMinute stats = result.get(0);
assertEquals("TEST_API", stats.getApiName());
assertEquals(1L, stats.getTotalCnt());
assertEquals(1L, stats.getSuccessCnt());
}
문제 해결
통계가 저장되지 않음
@EnableScheduling확인- 로그에서 스케줄러 실행 확인
- DB 연결 및 권한 확인
메모리 증가
- 활성 집계기 개수 확인
- 스케줄러 정상 동작 확인
- 차원 카디널리티 확인 (너무 많은 고유 키)
P50, P95 값이 이상함
- 응답시간 단위 확인 (ms)
- PropManager 설정값 확인 (
ApiStats그룹) - 히스토그램 범위 확인 (기본: 1~60000ms)
- 데이터 건수 확인 (최소 1건 이상)
PropManager 설정이 적용되지 않음
- DB에
ApiStats그룹 설정 확인 - PropManager 초기화 확인
- 로그에서 설정값 로딩 확인
- 설정 변경 후
PropManager.reload("ApiStats")실행
통계 수집을 비활성화하고 싶음
운영 중 성능 이슈 발생 시:
-- DB에서 비활성화
UPDATE TSEAIRM24 SET PROP_VALUE = 'false'
WHERE GROUP_NAME = 'ApiStats' AND PROP_KEY = 'enabled';
// 즉시 적용
PropManager.getInstance().reload("ApiStats");
이후 모든 recordEvent() 호출과 스케줄러 실행이 즉시 중단됩니다.
메모리에 쌓인 데이터는 유지되며, 다시 활성화 시 계속 수집됩니다.