Skip to content

10 Reliability and Observability

hywznn edited this page Jul 22, 2026 · 3 revisions

신뢰성 있는 실행과 관측성

외부 AI가 느리거나 실패해도 HR 요청을 잃지 않고, 같은 요청이 반복돼도 Task가 중복되지 않으며, 개인정보 없이 실패 구간을 찾을 수 있어야 합니다.

목표 흐름

POST /api/v1/ai-runs
→ AiRun + 실행 Event를 같은 DB transaction에 저장
→ 202 Accepted + aiRunId
→ background handler가 AI Runtime 호출
→ 응답 계약 검증
→ 결과 또는 명시적 실패 저장
→ Client가 상태 조회

Browser 연결이나 Server process가 끊겨도 DB의 Run과 Event를 이용해 이어서 처리합니다.

신뢰성 경계

flowchart TD
  R["POST /api/v1/ai-runs"] --> T["DB Transaction"]
  T --> RUN["AiRun = QUEUED"]
  T --> EVT["Event Publication = PENDING"]
  EVT --> H["Idempotent Handler"]
  H --> C["AiRuntimeClient"]
  C --> A["AI Runtime"]
  A --> P["LLM Provider"]
  A --> C
  C --> V["Server Contract · Core Value Validation"]
  V --> S["Persist Result / Failure"]
  H -. "temporary failure" .-> B["Backoff · Retry · Manual Command"]
  B --> H
Loading

Run만 저장하고 memory event를 발행하면 DB commit 직후 process가 종료될 때 실행 신호를 잃습니다. business 변경과 Event publication을 같은 transaction에 저장해 이 틈을 막습니다.

구현 선택

  1. package-by-feature modular monolith 경계를 먼저 지킵니다.
  2. Spring Modulith Event Publication Registry가 현재 Spring Boot/DB 조합에 맞는지 spike합니다.
  3. 적합하면 PostgreSQL-backed publication과 재발행을 사용합니다.
  4. 부적합하면 같은 DomainEventPublisher Port 뒤에 Transactional Outbox를 구현합니다.
  5. 처리량·팀 분리가 실제로 필요해질 때 Kafka/RabbitMQ를 검토합니다.

두 방법을 동시에 중복 구현하지 않습니다. 선택과 전환 기준은 #23 ADR에 남깁니다.

Event envelope

{
  "eventId": "uuid",
  "eventType": "AiRunRequested",
  "payloadVersion": 1,
  "aggregateType": "AI_RUN",
  "aggregateId": "uuid",
  "companyId": "uuid",
  "actorType": "HR_USER",
  "actorRef": "safe-reference",
  "requestId": "request-id",
  "traceId": "trace-id",
  "occurredAt": "2026-07-22T00:00:00Z",
  "payload": {
    "aiRunId": "uuid",
    "expectedVersion": 0
  }
}

Event에는 JWT, Worker token, 외국인등록번호, 여권번호, 전화번호, 계좌번호, Prompt, 자유 입력 전문을 넣지 않습니다. consumer가 다시 조회할 안전한 ID와 version만 둡니다.

Server Event 예시

Event 의미 대표 후속 처리
AiRunRequested Run 실행 요청이 저장됨 AI Runtime 호출 handler
AiRunSucceeded 계약 검증까지 완료됨 검토 projection·Audit
AiRunFailed 안전한 기술 실패가 확정됨 운영 지표·수동 재시도 안내
TaskCandidateAccepted HR이 후보를 Task로 확정함 Activity·Audit
TaskStatusChanged Domain command로 상태가 바뀜 Activity·후속 규칙
TaskApproved / TaskRejected 사람이 결정을 저장함 전달 가능 여부·Audit
WorkerLinkIssued 승인된 링크가 발급됨 Audit·전달 준비
WorkerResponded 근로자가 응답함 Task 후속 상태 검토
EvidenceAttached 완료 증빙이 연결됨 완료 guard 재평가
TaskCompleted 완료 조건을 통과함 Audit·Dashboard projection

Event 이름은 이미 일어난 사실을 과거형으로 표현합니다. Event 자체가 승인 권한을 만들지 않습니다.

Publication 상태

상태 의미 처리
PENDING commit됐지만 handler 미완료 실행 대상
PROCESSING 한 worker가 처리 중 lease·timeout 필요
RETRY_WAIT 일시 실패 후 대기 next_retry_at 이후 재실행
COMPLETED 처리 완료 일반 재처리 금지
FAILED 영구 오류 또는 retry 소진 운영자 확인·명시적 replay

Framework가 자체 상태를 제공하면 별도 table과 상태를 중복 만들지 않습니다.

멱등성

API Command

  • 필요한 변경 endpoint에 Idempotency-Key를 받습니다.
  • company + operation + key에 unique constraint를 둡니다.
  • 같은 key·같은 payload는 이전 결과를 반환합니다.
  • 같은 key·다른 payload는 IDEMPOTENCY_CONFLICT를 반환합니다.
  • 후보 결정과 Worker Link 회전을 동시에 호출해도 결과가 하나여야 합니다.

Event Consumer

  • event_id + consumer_name 또는 결과 unique constraint로 중복을 막습니다.
  • at-least-once 전달을 전제로 handler를 멱등하게 작성합니다.
  • optimistic lock과 expected version으로 오래된 Event가 최신 상태를 덮지 않게 합니다.
  • 외부 side effect와 DB 성공 기록 사이의 실패 지점을 설계합니다.

재시도 책임

실패 구간 책임 재시도 원칙
AI Runtime→Provider의 429/5xx ai 짧은 호출 단위의 제한적 retry·provider 정책
Server→AI Runtime timeout/503 server AiRun RETRYING, backoff, circuit breaker
JSON parse·Schema·version 불일치 자동 retry 안 함 계약 수정 또는 사람 검토
핵심값 손실·민감정보 탐지 자동 retry 안 함 후보 차단·quality/security 검토
Server 재시작·handler 중단 server Durable Event에서 미완료 publication 복구

두 계층의 retry 횟수를 곱하지 않도록 전체 시간과 시도 횟수 budget을 정합니다. Fake 후보를 fallback 성공으로 반환하지 않습니다.

Resilience4j 적용 위치

기능 Server 적용 위치 주의
HTTP timeout RemoteAiRuntimeClient DB transaction을 잡은 채 기다리지 않음
Retry 일시적 Internal API network/503 parse·business error 제외
Circuit Breaker AI Runtime endpoint open이면 빠르게 명시적 실패
Bulkhead AI integration 실행 자원 일반 HR API thread와 격리

Provider별 circuit·fallback은 AI Runtime의 책임입니다.

오류 분류

Code 범주 재시도 예시
AI_RUNTIME_TEMPORARY_* 제한적 자동/수동 timeout, 503
AI_RUNTIME_AUTH_* 자동 금지 S2S credential·audience 오류
AI_RESPONSE_PARSE_* 기본 자동 금지 malformed JSON
AI_RESPONSE_SCHEMA_* 자동 금지 required field·enum 위반
AI_CORE_VALUE_* 자동 금지·사람 검토 대상·날짜·금액·문서 손실
AI_PRIVACY_* 자동 금지·보안 검토 금지 개인정보 탐지
WORKFLOW_TRANSITION_* 입력·상태 수정 허용되지 않은 Task 전이
IDEMPOTENCY_CONFLICT 원 요청 확인 같은 key·다른 payload
CONCURRENT_MODIFICATION 최신 조회 후 재명령 version 불일치

내부 오류 전문과 stack trace를 Client에게 노출하지 않습니다.

관측성 소유권

sequenceDiagram
  participant C as Client
  participant S as Server
  participant D as PostgreSQL
  participant E as Server Event Handler
  participant A as AI Runtime
  participant P as Provider

  C->>S: X-Request-Id + trace context
  S->>D: AiRun + publication commit
  S-->>C: 202 + requestId + aiRunId
  E->>D: PENDING publication claim
  E->>A: traceparent 전달
  A->>P: AI Runtime child span
  P-->>A: response/error
  A-->>E: result/error + version metadata
  E->>D: safe result + publication 완료
  C->>S: GET /api/v1/ai-runs/{aiRunId}
  S->>D: AiRun 조회
  S-->>C: status + outcome + candidates/error
Loading
구간 소유자 기록 내용
Client→Server, Task Workflow, DB/Event server HTTP, 상태 전이, queue, Audit reference
Server→AI Runtime 양쪽 같은 trace context와 Internal latency
Agent Pipeline→Provider ai provider/model/agent span과 quality code
Collector, Dashboard, Alert infra 서비스별 telemetry 수집·보존

Server #26은 Server instrumentation과 trace propagation까지 담당합니다. Provider span과 prompt quality metric은 AI 저장소에 둡니다.

최소 Metric

영역 예시
HTTP 요청 수·오류율·p50/p95 latency
Task Workflow 생성·승인·완료 수, 상태 체류시간
AiRun queued 수, queue wait, 전체 latency, 성공·실패·outcome 비율
AI integration Internal timeout·circuit open·bulkhead reject
Validation parse·Schema·핵심값·privacy 실패
Event pending backlog·처리시간·retry·영구 실패
Worker Link 조회·응답·제출·만료·거부 수

company_id, worker_id, aiRunId, 이름, token, 자유 입력, Prompt, request_id, trace_id를 metric label로 쓰지 않습니다. 종류가 계속 늘어나는 값은 log/trace에서 안전하게 검색합니다.

Log·Trace 안전 규칙

  • Authorization, Cookie, Worker Link URL/token을 redact합니다.
  • request/response body 자동 수집을 기본 비활성 또는 allow-list로 제한합니다.
  • AI payload 전문 대신 크기·hash·version·validation code를 기록합니다.
  • 원본 파일명·OCR text·개인정보를 기록하지 않습니다.
  • observability 장애가 Task transaction을 rollback하지 않게 합니다.
  • AuditEvent와 일반 log의 보존 목적을 혼동하지 않습니다.

반드시 통과할 실패 테스트

  1. AiRun과 Event commit 직후 Server 종료 → 재시작 후 실행
  2. AI Runtime 성공 직후 handler 종료 → 중복 전달에도 결과 하나
  3. 같은 Idempotency-Key 동시 요청 → AiRun 하나
  4. 같은 후보 동시 ACCEPT → Task 하나
  5. Internal timeout/503 → retry budget과 circuit 동작
  6. malformed JSON·Schema 위반 → 후보·발송 없음
  7. 핵심값 변경 후 재승인 없이 Link 발급 → 거부
  8. Event retry 소진 → 추적 가능한 FAILED와 수동 replay
  9. 타 Company Run/Event/Audit 조회 → 차단
  10. fixture log·metric·trace에 token·PII 없음

운영자 확인 순서

  1. 사용자 응답의 requestIdaiRunId를 확인합니다.
  2. AiRun 상태, errorCode, retryCount, version을 봅니다.
  3. traceId로 Server와 AI Runtime 경계를 찾습니다.
  4. Event publication 상태와 다음 retry 시각을 확인합니다.
  5. 재시도 가능 오류인지 수동 입력·검토가 필요한지 판단합니다.
  6. replay 전에 idempotency와 aggregate version을 확인합니다.
  7. 해결 뒤 AuditEvent와 metric 회복을 확인합니다.

개인정보나 Secret을 추가 출력해 진단하지 않습니다.

구현 순서

  1. #23 계약·Event ADR
  2. #25 commit→restart→replay vertical slice
  3. #24 persistent AiRun·idempotency
  4. #8 RemoteAiRuntimeClient·장애 격리
  5. #11 approval·audit transaction
  6. #9 배포 recovery smoke
  7. #26 Server instrumentation·trace propagation

Clone this wiki locally