Skip to content

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/SpanPayloadTraceEntity/SpanEntity 컬럼 길이와 맞는 @Size 검증 추가 검토 (errorMessage, className, methodName, args 등). 현재는 컬럼 길이를 초과하는 값이 오면 400이 아니라 DB 삽입 단계에서 예외가 나 500으로 샐 수 있다 (Agent는 자체적으로 길이를 잘라 보내므로 정상 Agent 사용 시에는 발생하지 않음)