-
Notifications
You must be signed in to change notification settings - Fork 0
10 Reliability and Observability
외부 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
Run만 저장하고 memory event를 발행하면 DB commit 직후 process가 종료될 때 실행 신호를 잃습니다. business 변경과 Event publication을 같은 transaction에 저장해 이 틈을 막습니다.
- package-by-feature modular monolith 경계를 먼저 지킵니다.
- Spring Modulith Event Publication Registry가 현재 Spring Boot/DB 조합에 맞는지 spike합니다.
- 적합하면 PostgreSQL-backed publication과 재발행을 사용합니다.
- 부적합하면 같은
DomainEventPublisherPort 뒤에 Transactional Outbox를 구현합니다. - 처리량·팀 분리가 실제로 필요해질 때 Kafka/RabbitMQ를 검토합니다.
두 방법을 동시에 중복 구현하지 않습니다. 선택과 전환 기준은 #23 ADR에 남깁니다.
{
"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만 둡니다.
| 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 자체가 승인 권한을 만들지 않습니다.
| 상태 | 의미 | 처리 |
|---|---|---|
PENDING |
commit됐지만 handler 미완료 | 실행 대상 |
PROCESSING |
한 worker가 처리 중 | lease·timeout 필요 |
RETRY_WAIT |
일시 실패 후 대기 |
next_retry_at 이후 재실행 |
COMPLETED |
처리 완료 | 일반 재처리 금지 |
FAILED |
영구 오류 또는 retry 소진 | 운영자 확인·명시적 replay |
Framework가 자체 상태를 제공하면 별도 table과 상태를 중복 만들지 않습니다.
- 필요한 변경 endpoint에
Idempotency-Key를 받습니다. -
company + operation + key에 unique constraint를 둡니다. - 같은 key·같은 payload는 이전 결과를 반환합니다.
- 같은 key·다른 payload는
IDEMPOTENCY_CONFLICT를 반환합니다. - 후보 결정과 Worker Link 회전을 동시에 호출해도 결과가 하나여야 합니다.
-
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 성공으로 반환하지 않습니다.
| 기능 | 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
| 구간 | 소유자 | 기록 내용 |
|---|---|---|
| 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 저장소에 둡니다.
| 영역 | 예시 |
|---|---|
| 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에서 안전하게 검색합니다.
- 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의 보존 목적을 혼동하지 않습니다.
- AiRun과 Event commit 직후 Server 종료 → 재시작 후 실행
- AI Runtime 성공 직후 handler 종료 → 중복 전달에도 결과 하나
- 같은 Idempotency-Key 동시 요청 → AiRun 하나
- 같은 후보 동시
ACCEPT→ Task 하나 - Internal timeout/503 → retry budget과 circuit 동작
- malformed JSON·Schema 위반 → 후보·발송 없음
- 핵심값 변경 후 재승인 없이 Link 발급 → 거부
- Event retry 소진 → 추적 가능한
FAILED와 수동 replay - 타 Company Run/Event/Audit 조회 → 차단
- fixture log·metric·trace에 token·PII 없음
- 사용자 응답의
requestId와aiRunId를 확인합니다. - AiRun 상태,
errorCode,retryCount, version을 봅니다. -
traceId로 Server와 AI Runtime 경계를 찾습니다. - Event publication 상태와 다음 retry 시각을 확인합니다.
- 재시도 가능 오류인지 수동 입력·검토가 필요한지 판단합니다.
- replay 전에 idempotency와 aggregate version을 확인합니다.
- 해결 뒤 AuditEvent와 metric 회복을 확인합니다.
개인정보나 Secret을 추가 출력해 진단하지 않습니다.