05. Collector Design
목적
Collector의 책임 범위와 내부 패키지 구조를 정의한다.
범위
collector 모듈. Storage 세부 구현은 docs/06-storage-design.md, API 계약은 docs/07-api-design.md를 참고한다.
설계
패키지 구조 (io.apmx.collector)
text
io.apmx.collector
├── CollectorApplication Spring Boot 진입점
├── config
│ └── WebConfig Console dev 서버용 CORS 설정 (/api/** 한정)
├── ingest
│ ├── TraceIngestController POST /api/v1/ingest/traces
│ ├── IngestTraceRequest 요청 DTO (jakarta.validation, spans 포함)
│ ├── SpanPayload IngestTraceRequest에 중첩되는 Span 요청 DTO
│ ├── IngestTraceResponse 응답 DTO
│ ├── LogIngestController POST /api/v1/ingest/logs (배치)
│ ├── IngestLogRequest 로그 배치 요청 DTO / LogPayload: 로그 1줄
│ ├── IngestLogResponse 응답 DTO
│ ├── AgentIngestController POST /api/v1/ingest/agents (등록 + heartbeat upsert)
│ ├── RegisterAgentRequest Agent 등록 요청 DTO
│ └── IngestAgentResponse 응답 DTO
├── query
│ ├── ServiceController GET /api/v1/services, .../summary, .../uris
│ ├── TraceQueryController GET /api/v1/traces, GET /api/v1/traces/{traceId}
│ ├── LogQueryController GET /api/v1/logs, GET /api/v1/logs/services
│ ├── AgentQueryController GET /api/v1/agents, PATCH /api/v1/agents/{agentId}
│ └── *Response 조회용 응답 DTO (SpanResponse, LogEntryResponse, AgentResponse 포함)
├── trace
│ ├── Trace 불변 도메인 모델 (요청/응답 헤더, query string, Content 메타 포함)
│ ├── TraceEntity JPA 저장 모델
│ ├── TraceRepository 저장소 포트(인터페이스) - REST 레이어는 이것만 의존
│ ├── TraceRepositoryImpl JPA 기반 어댑터 (헤더 Map ↔ JSON 직렬화 포함)
│ ├── TraceJpaRepository Spring Data JPA 인터페이스
│ ├── Span 불변 도메인 모델 (`@ApmxTrace` 메서드 호출 스택 1건)
│ ├── SpanEntity JPA 저장 모델
│ ├── SpanRepository 저장소 포트(인터페이스)
│ ├── SpanRepositoryImpl JPA 기반 어댑터
│ ├── SpanJpaRepository Spring Data JPA 인터페이스
│ └── *Projection 집계 쿼리 결과 매핑용 인터페이스
├── log
│ ├── LogEntry 불변 도메인 모델 (로그 1줄)
│ ├── LogEntryEntity JPA 저장 모델 (테이블명 app_log)
│ ├── LogRepository 저장소 포트(인터페이스)
│ ├── LogRepositoryImpl JPA 기반 어댑터 (서비스/레벨 필터)
│ └── LogEntryJpaRepository Spring Data JPA 인터페이스
└── agent
├── AgentInfo 불변 도메인 모델 (보고값 + Collector 오버라이드)
├── AgentEntity JPA 저장 모델 (agentId unique)
├── AgentRepository 저장소 포트(인터페이스)
├── AgentRepositoryImpl JPA 기반 어댑터 (heartbeat upsert, 오버라이드 관리)
└── AgentJpaRepository Spring Data JPA 인터페이스설계 원칙
- 수신 DTO와 저장 모델 분리:
IngestTraceRequest(ingest 계약) →Trace(도메인) →TraceEntity(저장)로 단계별 변환한다. API 계약이 바뀌어도 저장 모델에 영향이 없고, 반대도 마찬가지다. - Collector는 분석하지 않는다: 평균 응답시간/에러율 계산은 단순 집계 쿼리(JPQL)로만 수행하고, 복잡한 분석 로직은 넣지 않는다. 향후 대시보드/분석 컴포넌트로 옮길 수 있도록 최소한으로 유지한다.
- Idempotent ingest: 같은
traceId가 중복 수신되면(Agent 재전송 등) 무시한다 (TraceRepositoryImpl.save()가existsByTraceId체크 후 skip). - 검증 실패는 500이 아닌 400으로 응답:
MethodArgumentNotValidException을 각 ingest 컨트롤러에서 잡아{"success": false, "message": "..."}로 응답한다. - 로그 ingest 정규화: 레벨이 없으면
INFO, 알 수 없는 소스는stdout으로 정규화하고, 메시지는 8KB로 절단한다. 배치는 1회 최대 1000건으로 제한한다. - Agent upsert와 오버라이드 분리: heartbeat는 Agent가 보고하는 값(
agentName,pid,jvmVersion,lastSeenTime등)만 갱신하고, Collector에서 지정한displayName/groupName은 건드리지 않는다. 오버라이드는PATCH /api/v1/agents/{agentId}로만 변경되며 Agent 재시작 후에도 유지된다. Agent 활성 여부는 마지막 heartbeat로부터 90초 기준으로 계산한다.
요청 처리 흐름 (ingest)
text
Agent → POST /api/v1/ingest/traces
→ @Valid 검증 (jakarta.validation)
→ IngestTraceRequest → Trace 매핑
→ TraceRepository.save(Trace)
→ { "success": true }예시
bash
curl -X POST http://localhost:8080/api/v1/ingest/traces \
-H "Content-Type: application/json" \
-d '{
"traceId": "abc123",
"serviceName": "order-service",
"hostName": "was-01",
"language": "java",
"runtime": "spring-boot",
"method": "GET",
"uri": "/orders",
"statusCode": 200,
"startTime": 1710000000000,
"endTime": 1710000000150,
"error": false,
"errorMessage": null
}'
# {"success":true,"message":null}TODO
- ingest 요청 rate limit / 사이즈 제한
- Batch ingest API (
POST /api/v1/ingest/traces/batch) 추가 검토 - Collector 자체 메트릭(수신량, 저장 실패율)을 Actuator로 노출
IngestTraceRequest/SpanPayload에TraceEntity/SpanEntity컬럼 길이와 맞는@Size검증 추가 검토 (errorMessage,className,methodName,args등). 현재는 컬럼 길이를 초과하는 값이 오면 400이 아니라 DB 삽입 단계에서 예외가 나 500으로 샐 수 있다 (Agent는 자체적으로 길이를 잘라 보내므로 정상 Agent 사용 시에는 발생하지 않음)