예를 들어 다음 구조를 만들었다고 하자.
외부 알림톡 API
Timeout
5초
Retry
최대 3회
Circuit Breaker
실패율 증가 시 OPEN
Queue
재처리 가능
Reconciliation
UNKNOWN 상태 복구
코드만 보면 꽤 안전해 보인다.
하지만 실제 장애가 발생하면 전혀 예상하지 못한 문제가 나올 수 있다.
Timeout은 걸리지만 HTTP Connection이 안 닫힘
Retry가 동시에 몰려 Retry Storm 발생
Circuit Breaker가 열렸지만 Queue Worker는 계속 요청
Worker 재시작 후 동일 Job 중복 실행
UNKNOWN Job이 Reconciliation 대상에 포함되지 않음
즉:
장애 대응 로직 역시 장애 상황에서 테스트해야 한다.
일반적인 테스트에서는 대부분 정상 상황을 검증한다.
주문 생성 성공
알림톡 발송 성공
Webhook 처리 성공
Export 완료
실패 테스트를 넣더라도:
provider.send.mockRejectedValue(
new Error('timeout'),
);
정도에 그치는 경우가 많다.
하지만 실제 장애는 훨씬 복잡하다.
예:
Provider 응답이 30초 동안 오지 않음
500 Error가 간헐적으로 발생
첫 요청은 성공했지만 응답만 유실
DB 연결이 10초 동안 끊김
Worker가 작업 도중 종료
Queue에는 같은 Job이 두 번 전달
Webhook이 순서가 뒤집혀 도착
이런 상황까지 검증해야 운영 신뢰성이 높아진다.
Failure Injection은 의도적으로 실패 상황을 만들어보는 것이다.
예:
외부 API Timeout 발생시키기
DB Connection 실패시키기
Worker 강제 종료
HTTP 500 반환
Latency 인위적으로 증가
Webhook 중복 전달
Queue Job 중복 실행
목표는 서비스를 망가뜨리는 것이 아니다.
목표는:
실패했을 때
우리가 설계한 방어 장치가
정말 작동하는가?
를 확인하는 것이다.
실무에서는 용어가 겹쳐 사용되기도 하지만 개념적으로는 이렇게 이해하면 편하다.
Failure Injection
특정 실패를 의도적으로 발생시켜
특정 방어 로직을 테스트
예:
Notification Provider Timeout
반면:
Chaos Testing
실제 시스템에 예상치 못한 장애를 주입하고
전체 서비스가 얼마나 버티는지 검증
조금 더 넓은 개념이다.
현재 프로젝트에서는 거대한 Chaos Engineering 플랫폼보다 작은 Failure Injection 테스트부터 시작하는 것이 맞다.
처음부터 운영 서버에서:
DB 끊어보기
Worker 죽이기
Network 차단
Random 500 발생
같은 실험을 하면 위험하다.
우선 순서는:
Unit Test
↓
Integration Test
↓
Local
↓
Staging
↓
Controlled Production Test
가 되어야 한다.
현재 규모에서는 대부분:
Local + Staging
까지만 해도 충분한 효과가 있다.
Chaos Testing을 단순히:
서버를 랜덤하게 죽여본다
라고 생각하기 쉽다.
하지만 중요한 것은 먼저 정상 상태를 정의하는 것이다.
예:
정상 상태
주문 성공률 > 99%
Notification Queue Lag < 30초
UNKNOWN Job < 5건
그리고 장애를 주입한다.
Provider Timeout 발생
그 후 확인한다.
주문 생성은 정상인가?
Queue가 폭주하지 않는가?
Circuit Breaker가 열리는가?
Provider 정상화 후 자동 복구되는가?
Chaos Testing에서는 정상 상태를 Steady State라고 생각할 수 있다.
예:
Order API
Success Rate
99.9%
p95
500ms
Notification
Queue Delay
< 10s
Failure Rate
< 1%
실험 전후로 이 기준이 어떻게 변하는지 본다.
무작정 장애를 넣는 것이 아니다.
먼저 가설을 만든다.
예:
가설
알림톡 Provider가 5분 동안 장애가 발생해도
고객 주문 생성 기능은 정상적으로 동작한다.
그 이유는:
주문 생성
↓
DB Commit
↓
Queue
↓
Notification Worker
로 분리되어 있기 때문이다.
실험 후:
Order API
정상
Notification Queue
Pending 증가
Retry
Backoff 정상
Circuit Breaker
OPEN
Provider 복구 후
Queue 정상 처리
라면 설계가 제대로 작동한 것이다.
반대로 주문 API까지 느려진다면:
Notification Provider가
Order Request 흐름에 아직 결합되어 있다.
는 문제를 발견할 수 있다.
현재 프로젝트에서는 다음이 현실적인 대상이다.
1. 알림톡 Provider Timeout
2. 알림톡 Provider 500
3. Worker 강제 종료
4. Queue 중복 Job
5. Webhook 중복 수신
6. Webhook 순서 역전
7. PostgreSQL 일시 연결 실패
8. Export 중 S3 업로드 실패
9. AI Run 도중 프로세스 종료
10. 배포 후 Health Check 실패
정상 흐름:
Notification Worker
↓
Provider
↓
200 OK
장애 실험:
Notification Worker
↓
Provider
응답 20초 지연
우리 Timeout이 5초라면:
5초 후 Timeout
↓
UNKNOWN 또는 Retry 판단
↓
Backoff
↓
Circuit Breaker 상태 반영
되어야 한다.
단순히:
Timeout 발생 여부
만 보면 부족하다.
확인해야 할 것은:
HTTP Connection이 해제되는가?
Worker Thread가 계속 점유되지 않는가?
Retry가 즉시 몰리지 않는가?
Job 상태가 정확한가?
Correlation ID가 유지되는가?
Error Metric이 증가하는가?
이다.
예:
Provider
첫 요청
500
두 번째
500
세 번째
200
이 상황에서:
Retry 2회
최종 SUCCESS
가 되어야 한다.
그리고 로그에는:
attempt = 1
FAILED
attempt = 2
FAILED
attempt = 3
SUCCESS
가 남아야 한다.
Retry를:
1초
2초
4초
8초
처럼 두었다면 실제로 그 간격을 지키는지 확인한다.
잘못 구현되어:
1초
1초
1초
1초
가 되면 Provider 장애 상황에서 부하를 키울 수 있다.
모든 Worker가 똑같이:
4초 후 Retry
하면 동시에 다시 요청한다.
그래서:
3.7초
4.4초
4.1초
3.9초
처럼 약간의 랜덤성을 준다.
이를 Jitter라고 한다.
테스트에서는 정확한 값이 아니라:
허용 범위 내 분산
을 확인하면 된다.
예:
최근 20회 요청 중
15회 실패
가 Threshold를 넘었다고 하자.
Circuit 상태:
CLOSED
↓
OPEN
되어야 한다.
그리고 새로운 요청은 Provider에 실제로 전달되지 않아야 한다.
예:
Circuit = OPEN
일 때:
NotificationJob
은 삭제하거나 실패 처리하면 안 된다.
보통:
Retry Pending
또는
Queue 대기
상태로 유지해야 한다.
Provider 장애가 주문 데이터 손실로 이어져서는 안 된다.
일정 시간이 지나면 Circuit Breaker는 일부 요청만 보내 Provider가 복구됐는지 확인할 수 있다.
OPEN
↓ 일정 시간
HALF_OPEN
↓ 테스트 요청
성공하면:
CLOSED
실패하면:
OPEN
으로 돌아간다.
이 상태 전환 역시 테스트해야 한다.
확인:
OPEN 전환 조건
OPEN 유지 시간
HALF_OPEN 요청 개수
복구 후 CLOSED 전환
Metric 기록
Log 기록
이다.
작업 도중 Worker가 죽는 상황도 중요하다.
예:
Job Claim
↓
PROCESSING
↓
외부 API 호출
↓
Worker 강제 종료
DB에는:
PROCESSING
만 남을 수 있다.
0916~0917에서 만든 구조가 있다면:
PROCESSING
↓
heartbeat 중단
↓
lease 만료
↓
Stale Job 감지
↓
Reconciliation
↓
RETRY_PENDING 또는 SUCCESS
로 복구되어야 한다.
Job이 영원히 PROCESSING으로 남지 않는가?
다른 Worker가 Lease 만료 전 가져가지 않는가?
Lease 만료 후 안전하게 회수되는가?
외부 요청이 이미 성공했다면 중복 실행하지 않는가?
특히 마지막이 중요하다.
매우 까다로운 케이스다.
Provider 요청
↓
Provider SUCCESS
↓
Worker가 DB 업데이트 전 종료
DB:
PROCESSING
외부:
SUCCESS
이때 단순 Retry하면 중복 발송될 수 있다.
따라서:
providerMessageId
idempotencyKey
Reconciliation
가 필요하다.
예:
Provider Send
SUCCESS
↓ 인위적 Crash
DB Update 수행 안 함
그 후 Reconciler가:
Provider 상태 조회
↓
DELIVERED
↓
Job SUCCESS 복구
하는지 검증한다.
이 테스트 하나가 0916~0917 설계 대부분을 실제로 검증한다.
Queue 시스템에서는 같은 메시지가 두 번 전달될 수 있다고 가정하는 편이 안전하다.
예:
job_123
job_123
두 Worker가 받는다.
Atomic Claim 또는 Idempotency가 있다면:
Worker A
Claim 성공
→ 실행
Worker B
Claim 실패
→ Skip
이 되어야 한다.
최종 결과:
알림톡 1회
Audit Log 1회
상태 변경 1회
여야 한다.
이건 실제로 Race Condition을 찾는 데 매우 중요하다.
Promise.all([
execute(jobId),
execute(jobId),
execute(jobId),
]);
처럼 동시에 실행시켜본다.
결과가:
외부 Side Effect
1회
인지 확인한다.
외부 Provider는 같은 Webhook을 다시 보낼 수 있다.
예:
providerEventId = evt_123
를:
1회
2회
3회
전송한다.
DB에는:
WebhookEvent
1건
만 처리되어야 한다.
더 어려운 상황은 이벤트 순서가 뒤집히는 것이다.
원래:
SENT
↓
DELIVERED
인데 실제 수신:
DELIVERED
↓
SENT
일 수 있다.
두 번째 이벤트 때문에 상태가 다시:
SENT
로 후퇴하면 안 된다.
예:
PENDING
→ SENT
→ DELIVERED
인 경우:
DELIVERED
→ SENT
는 금지한다.
즉 상태 머신에서도:
Allowed Transition
을 정의해야 한다.
예:
DB 연결 10초 차단
시 확인한다.
API가 무한 대기하지 않는가?
Connection Timeout이 있는가?
HTTP 500/503이 적절히 반환되는가?
Queue Worker가 폭주하지 않는가?
DB 복구 후 자동 정상화되는가?
예:
Transaction Commit 직후
Connection 끊김
이면 실제 Commit 여부가 불확실할 수 있다.
따라서 Write 작업은 특히:
무조건 Retry
가 위험할 수 있다.
Idempotency와 상태 확인이 필요하다.
예:
Order 저장 성공
Audit Log 저장 실패
같은 상황을 일부러 만든다.
하나의 Transaction이어야 한다면 최종 결과는:
둘 다 Rollback
이어야 한다.
0826에서 배운 Transaction Boundary와 연결된다.
Order 상태 변경
Audit Log
Outbox Event
가 하나의 일관된 변경이어야 한다면 실패 주입 후:
일부만 저장
되는지 확인한다.
예:
DB 변경
SUCCESS
Outbox Worker
FAILED
여기서 비즈니스 데이터까지 다시 Rollback할 수는 없다.
대신:
Outbox Event
Pending 유지
↓
Worker 복구
↓
후속 Event 발행
이 되어야 한다.
Export 구조에서:
Excel 생성
SUCCESS
↓
S3 Upload
FAILED
할 수 있다.
이때:
ExportJob 전체 재생성
보다:
이미 생성한 Artifact 유지
↓
Upload Step부터 Resume
할 수 있다면 효율적이다.
예:
ExportJob
1. Query Data
SUCCESS
2. Generate Excel
SUCCESS
3. Upload S3
FAILED
재시작:
Step 3
부터 가능하다.
이런 구조는 AI Run에도 그대로 적용된다.
예:
Analyse
SUCCESS
Modify
SUCCESS
Test
실행 중
↓
AI Runner 종료
다시 실행할 때:
Analyse부터 재실행
하는 것이 아니라 현재 상태를 Reconcile해야 한다.
Git Commit
git diff
생성된 Artifact
Test Report
Process 상태
Checkpoint
Approval
를 확인한다.
예:
Step 1 Analyse
SUCCESS
Step 2 Modify
SUCCESS
Step 3 Test
SUCCESS
Step 4 Report
FAILED
Resume 시:
Report
부터 다시 시작해야 한다.
그리고 이전 파일 수정이 중복 실행되지 않아야 한다.
AI가 Tool을 실행했는데:
npm test
가 무한 대기한다고 하자.
Tool에도 Timeout이 필요하다.
예:
test.run
maxDuration
5분
초과:
TOOL_TIMEOUT
으로 중단한다.
Timeout 처리했다고 부모 프로세스만 종료하면:
npm
↓
jest
↓
child process
등이 남을 수 있다.
그러면 다음 Run에 영향을 줄 수 있다.
따라서 Tool Runner는 종료 시 Child Process까지 정리되는지 확인해야 한다.
부분 수정된 파일이 남는가?
Checkpoint가 정확한가?
Retry가 같은 수정 작업을 중복 적용하는가?
Test Process가 남는가?
새 Run에서 이전 Run Artifact를 잘못 사용하는가?
등을 검증한다.
코드에 개발/테스트 전용 실패 주입 기능을 둘 수 있다.
예:
if (
config.failureInjection
.notificationTimeout
) {
await sleep(20_000);
}
단 반드시:
Production에서 기본 OFF
여야 한다.
예:
MockNotificationProvider
를 만든다.
설정에 따라:
SUCCESS
TIMEOUT
HTTP_500
RATE_LIMIT
UNKNOWN
등을 반환하게 한다.
type MockScenario =
| 'SUCCESS'
| 'TIMEOUT'
| 'SERVER_ERROR'
| 'RATE_LIMIT'
| 'CONNECTION_RESET';
테스트에서 원하는 상황을 정확하게 재현할 수 있다.
예:
NotificationService
↓
NotificationProvider
↓
KakaoProvider
테스트에서는:
NotificationService
↓
MockProvider
를 넣는다.
이게 Repository/Adapter 구조를 사용하는 실질적인 장점 중 하나다.
장애 테스트 시나리오를 목록으로 관리할 수도 있다.
예:
F001
Notification Provider Timeout
F002
Notification HTTP 500
F003
Worker Crash After External Success
F004
Duplicate Queue Delivery
F005
Duplicate Webhook
F006
Out-of-order Webhook
F007
DB Connection Failure
F008
S3 Upload Failure
예:
F003
Scenario
외부 알림톡 성공 후 Worker Crash
Expected
Job:
PROCESSING → UNKNOWN → SUCCESS
External Send:
1회
Reconciliation:
수행
Duplicate Send:
0건
Audit:
Recovery 기록
이렇게 하면 테스트 기준이 명확하다.
표로 관리해도 좋다.
| 장애 | 기대 방어 | 최종 상태 |
|---|---|---|
| Provider Timeout | Timeout + Retry | SUCCESS / UNKNOWN |
| Provider 장기 장애 | Circuit Breaker | RETRY_PENDING |
| Worker Crash | Lease + Reconcile | 복구 |
| 중복 Queue | Idempotency | 1회 실행 |
| 중복 Webhook | Unique Event | 1회 처리 |
| S3 실패 | Step Resume | 재업로드 |
| AI Crash | Checkpoint | Resume |
이 표만 있어도 전체 자동화 안정성을 보기 쉽다.
예:
Provider Timeout 발생
자체는 테스트 성공이 아니다.
실제 성공 기준은:
주문 기능 영향 없음
Job 손실 없음
중복 발송 없음
Queue 폭주 없음
자동 복구 가능
로그 추적 가능
이다.
장애가 어디까지 영향을 미치는지를 Blast Radius라고 표현한다.
예:
Notification Provider 장애
좋은 구조
Notification만 영향
반대로:
Notification 장애
↓
Order API Timeout
↓
고객 신청 실패
↓
관리자 API까지 느려짐
이면 Blast Radius가 너무 크다.
결국:
장애를 발생시키는 것
보다:
Blast Radius가 예상대로 제한되는지 확인
하는 것이 중요하다.
0924의 Bulkhead 구조도 실제로 검증해야 한다.
예:
Export Worker
100개 작업 폭주
시:
Notification Worker
가 영향을 받지 않아야 한다.
예:
Export
CPU
Connection
Concurrency
를 과도하게 사용했을 때:
Order API
Notification
Webhook
까지 느려지는지 확인한다.
모든 Worker가 같은 Connection Pool을 무제한 사용하면:
Export 폭주
↓
DB Connection 소진
↓
Order API 실패
할 수 있다.
따라서 중요 기능별 Concurrency 제한이 필요할 수 있다.
외부 API가:
초당 10건
제한인데 Worker가:
초당 100건
보내면 429가 발생할 수 있다.
테스트에서는 의도적으로 낮은 Rate Limit을 걸어:
429 처리
Retry-After
Backoff
Queue 증가
가 정상인지 확인한다.
처리 속도보다 Job 생성 속도가 빠르면 Queue가 계속 쌓인다.
생성
100/s
처리
20/s
이라면:
Queue 증가
는 당연하다.
이때 시스템에는 Backpressure 전략이 필요하다.
예:
Concurrency 제한
요청 속도 제한
Batch 처리
우선순위 Queue
일부 비핵심 작업 지연
생산자 속도 제한
등이 있다.
Queue Pending이:
10
100
1,000
100,000
으로 증가한다면 언젠가 복구 자체가 어려워질 수 있다.
따라서:
Queue Depth
Oldest Job Age
Growth Rate
를 같이 본다.
예:
Worker 10분 정지
한다.
그동안 Queue가 쌓인다.
Worker를 복구했을 때:
정상 속도로 backlog 감소
Provider Rate Limit 초과 없음
DB 부하 급증 없음
인지 확인한다.
장애 중:
Queue 10,000건
쌓였다.
Provider 복구 후 Worker가 전부 동시에 실행하면:
Recovery Storm
이 발생할 수 있다.
Concurrency 제한
Rate Limiting
Gradual Ramp-up
Retry Jitter
Batch Size 제한
으로 천천히 복구한다.
0922의 SLO와 연결해서:
Recovery Throughput
Queue Drain Time
MTTR
를 측정할 수 있다.
예:
Pending 10,000건
30분 이내 정상화
같은 기준이다.
GameDay는 특정 날짜에 장애 상황을 정해서 실제 대응 훈련을 하는 것이다.
예:
가정
오늘 알림톡 Provider가
30분 동안 장애 발생
그리고 실제로 Staging에서 상황을 재현한다.
테스트 코드만 검증하는 것이 아니다.
Alert가 오는가?
Dashboard에서 찾을 수 있는가?
Runbook이 실제로 도움이 되는가?
복구 절차를 알고 있는가?
정상화 여부를 확인할 수 있는가?
같은 운영 프로세스를 같이 검증한다.
대규모 조직처럼 정기적인 장애 훈련까지 할 필요는 없다.
하지만 중요한 기능이라면:
배포 전
큰 이벤트 전
사전예약 전
신규 자동화 도입 전
한 번씩 해볼 가치가 있다.
특히 휴대폰 사전예약처럼 특정 기간에 트래픽과 문의가 몰리는 서비스라면 더 그렇다.
예:
시나리오
아이폰 사전예약 첫날
Notification Provider 10분 장애
확인:
고객 신청은 정상
NotificationJob Queue 누적
중복 발송 없음
Provider 정상화 후 순차 처리
관리자에서 Pending 확인 가능
Critical Alert 발생
Runbook으로 대응 가능
시나리오
관리자 Excel 대량 다운로드
+
사전예약 주문 증가
확인:
Export가 DB Connection을 독점하지 않는가?
주문 API Latency가 유지되는가?
Export Worker Concurrency Limit가 작동하는가?
이것은 Bulkhead 검증이다.
배포 후
Health Check 실패
를 가정한다.
확인:
Release 상태 감지
Traffic 전환 중단
Rollback 가능
이전 버전 정상
Incident Timeline 기록
등이다.
예:
AI가 파일 수정
↓
테스트 성공
↓
Report 생성 중 Agent 종료
복구:
Run Reconciliation
↓
Checkpoint 확인
↓
Report Step부터 Resume
되는지 확인한다.
최소한:
Production 데이터 사용 금지
실제 고객 알림 발송 금지
테스트 Provider 사용
테스트 계정 사용
Kill Switch 준비
실험 종료 조건 정의
가 필요하다.
실험 중 예상보다 영향이 크면 즉시 중단해야 한다.
예:
Order API Error Rate > 1%
Production 요청 영향
DB CPU > 80%
예상치 못한 외부 발송 발생
하면 즉시 종료한다.
예:
Failure Injection Duration
최대 5분
처럼 정한다.
실험 코드가 잘못되어 장애 상태가 계속 유지되는 것을 막는다.
Feature Flag 방식으로 장애 주입 기능을 만든다면:
enabledUntil
을 둘 수 있다.
예:
현재 시각 < enabledUntil
인 경우에만 동작한다.
시간이 지나면 자동 비활성화한다.
실패 주입 코드를 포함한다면 최소한:
environment === 'production'
에서는 차단하는 것이 안전하다.
또는 별도의 매우 강한 승인 구조가 필요하다.
현재 프로젝트에서는 Production Failure Injection 자체를 하지 않는 편이 낫다.
Failure Injection은 기능 테스트뿐 아니라 Observability 테스트이기도 하다.
예:
Provider Timeout
발생 시:
Error Code
Correlation ID
Job ID
Attempt
Circuit State
가 로그에 남는지 확인한다.
같은 상황에서:
notification_failed_total
retry_total
circuit_open
queue_pending
unknown_jobs
등이 실제로 증가하는지 본다.
예:
Provider Error Rate 50%
를 만들었는데 아무 Alert도 오지 않는다면:
Monitoring은 있지만
실제 장애 감지는 못 하는 상태
다.
반대로 한 장애에서:
Alert 50개
가 온다면 운영하기 어렵다.
같은 Incident로 묶거나 Severity별로 줄여야 한다.
GameDay에서 Runbook을 실제로 따라가본다.
그러면:
명령어가 틀림
메뉴 위치가 달라짐
필요한 로그 검색 방법이 없음
정상화 기준이 애매함
등을 발견할 수 있다.
Runbook은 실제로 써봐야 품질을 알 수 있다.
GameDay 종료 후 간단하게 기록한다.
무엇을 테스트했는가?
가설은 맞았는가?
어떤 방어 장치가 작동했는가?
어떤 문제가 발견됐는가?
어떤 Action Item이 생겼는가?
실제 Incident와 똑같은 형식으로 정리해도 좋다.
Experiment
Notification Provider Timeout
Hypothesis
주문 기능은 영향을 받지 않는다.
Result
PASS
Observed
Order API
정상
Notification Queue
최대 83건
Circuit
OPEN 정상
Recovery
Provider 정상화 후 4분 내 Queue 처리
Issue
UNKNOWN Metric이 Dashboard에 없음
Action
UNKNOWN Gauge 추가
실험에서:
설계대로 동작하지 않음
이 나와도 테스트 실패가 아니다.
오히려 실제 고객 장애 전에 문제를 발견한 것이다.
예:
Worker Crash 후
Job이 계속 PROCESSING
을 발견했다면:
Stale Recovery 구현 누락
을 실제 장애 전에 알게 된 것이다.
발견한 장애 시나리오는 테스트에 추가한다.
예:
2026-09-25
Incident:
Worker Crash After Provider Success
이후:
integration test
로 고정한다.
다시는 같은 문제가 코드 변경으로 재발하지 않도록 한다.
좋은 운영 문화는:
Incident
↓
Postmortem
↓
Action Item
↓
Regression Test
로 이어진다.
장애를 문서화만 하고 끝내지 않는다.
예:
F009
Worker Crash after
Provider Response
before DB Update
처럼 실제 발생한 Incident를 Failure Scenario로 추가한다.
시간이 지날수록 시스템 고유의 장애 테스트 자산이 쌓인다.
처음에는:
Manual
로 해도 충분하다.
예:
Mock Provider TIMEOUT 설정
↓
테스트 주문 생성
↓
Dashboard 확인
이후 반복 가치가 큰 시나리오는 CI에 넣는다.
예:
Duplicate Queue
Duplicate Webhook
Transaction Rollback
Idempotency
Retry Limit
Resume
State Transition
등이다.
빠르고 재현성이 높기 때문이다.
예:
실제 Network Delay
Process Crash
DB Failover
대규모 Load
Recovery Storm
등은 별도 Integration 환경에서 실행하는 편이 낫다.
많이
Unit Test
Integration Test
↓
적당히
Failure Scenario Test
↓
적게
GameDay
Chaos Experiment
정도로 생각하면 된다.
모든 테스트를 Chaos Test로 만들 필요는 없다.
현재 프로젝트에서:
Chaos Monkey
Service Mesh Fault Injection
Kubernetes Pod Random Kill
Multi-region Failover GameDay
까지 할 필요는 없다.
오히려 유지보수 부담만 늘어난다.
투게더몰이라면:
Provider 장애
Worker 중단
DB 연결 문제
Webhook 중복
Export 실패
배포 실패
AI 자동화 중단
정도가 핵심이다.
만약 내부 테스트 API를 만든다면:
POST /internal/test/failure
같은 Endpoint를 외부에서 접근할 수 있게 만들면 위험하다.
반드시:
개발 환경 전용
Authentication
Allowlist
Production Disable
같은 보호가 필요하다.
가능하면 테스트 설정을:
Environment
Mock Adapter
Test Fixture
등으로 제어한다.
운영 API에 Failure Injection 기능을 섞지 않는다.
테스트 로그에는 실제 장애와 구분할 수 있도록:
testScenarioId
를 넣어도 좋다.
예:
{
"event": "notification.send.failed",
"testScenarioId": "F001",
"errorCode": "PROVIDER_TIMEOUT"
}
AI에게 프로젝트 구조를 분석시켜:
이 시스템에서 발생 가능한
failure point를 찾아줘.
라고 할 수 있다.
예:
DB
External API
Queue
File System
S3
Git
LLM
Deployment
별로 시나리오를 찾아낼 수 있다.
초기에는:
AI
→ 시나리오 제안
Human
→ 선택
Test Harness
→ 실행
구조가 안전하다.
예:
Scenario
Webhook duplicate delivery
Expected Protection
providerEventId UNIQUE
Risk
LOW
Environment
Integration Test
Production Impact
NONE
같이 위험도를 먼저 평가하게 한다.
실험 후:
Logs
Metrics
Timeline
Expected Result
를 구조화해서 제공하면 AI가:
가설 통과 여부
비정상 지표
누락된 방어 장치
추천 Action Item
을 정리할 수 있다.
현재 Work Report 자동화에도:
### Reliability Test
Scenario
Notification Timeout
Result
PASS
Found Issue
Queue Dashboard 개선 필요
Action
UNKNOWN Metric 추가
같은 내용을 넣을 수 있다.
실제로 시스템 안정성을 개선한 기록으로 남는다.
단순:
Retry 기능 구현
보다:
외부 API Timeout, Worker Crash,
중복 Queue/Webhook 시나리오를 테스트하고
Idempotency, Reconciliation,
Circuit Breaker가 정상적으로
동작하는지 검증
했다고 설명하면 실제 운영 경험을 보여주기 좋다.
단 실제 적용한 범위만 이야기해야 한다.
test/
└─ reliability/
├─ notification/
│ ├─ provider-timeout.spec.ts
│ ├─ provider-500.spec.ts
│ └─ duplicate-job.spec.ts
│
├─ webhook/
│ ├─ duplicate.spec.ts
│ └─ out-of-order.spec.ts
│
├─ export/
│ └─ s3-failure.spec.ts
│
└─ ai/
└─ resume-after-crash.spec.ts
interface FailureScenario {
id: string;
name: string;
hypothesis: string;
fault: string;
expectedResult: string[];
maxDurationMs: number;
environment:
| 'test'
| 'staging';
}
interface FailureExperimentResult {
scenarioId: string;
startedAt: Date;
completedAt: Date;
status:
| 'PASS'
| 'FAIL'
| 'ABORTED';
observations: string[];
actionItems: string[];
}
현재 프로젝트에서는 이 다섯 개가 가장 ROI가 높다.
1.
동일 NotificationJob 동시 실행
2.
중복 Webhook
3.
Provider Timeout + Retry
4.
Worker Crash + Stale Recovery
5.
Transaction 중간 실패
이 다섯 가지는 운영 장애와 직접 연결될 가능성이 높다.
6.
Circuit Breaker OPEN / HALF_OPEN
7.
Queue Backlog Recovery
8.
S3 Upload Resume
9.
AI Run Resume
10.
Deployment Health Check Failure
순으로 늘린다.
현재 NestJS + Prisma 기반 프로젝트에
Reliability / Failure Scenario 테스트 구조를 추가해줘.
목표는 Production에 장애를 발생시키는
Chaos Engineering 플랫폼을 만드는 것이 아니라,
현재 구현된
Retry, Idempotency, Reconciliation,
Lease, Heartbeat, Circuit Breaker 등의
장애 대응 로직이 실제 실패 상황에서도
정상적으로 동작하는지 검증하는 것이다.
Production 코드에 위험한
Failure Injection Endpoint를 만들지 않는다.
우선 Test / Integration 환경에서만 동작하게 한다.
1. reliability 전용 테스트 폴더를 만든다.
예:
test/reliability/
notification/
webhook/
automation/
export/
2. Notification Provider 인터페이스에
테스트에서 사용할 Mock Provider를 구현한다.
Mock Provider는 다음 Scenario를 지원한다.
- SUCCESS
- TIMEOUT
- HTTP_500
- RATE_LIMIT
- CONNECTION_RESET
3. Provider TIMEOUT 테스트를 작성한다.
확인:
- Timeout 적용
- Retry 등록
- Attempt Count 증가
- Error Code 기록
- Correlation ID 유지
4. Provider HTTP 500 → 500 → SUCCESS
시나리오를 만든다.
최종 결과가 SUCCESS이고
중복 Side Effect가 발생하지 않는지 확인한다.
5. 동일 NotificationJob을
동시에 여러 Worker가 실행하는 상황을 테스트한다.
Atomic Claim 또는 Idempotency에 의해
실제 Side Effect가 1회만 실행되어야 한다.
6. 중복 Webhook 테스트를 만든다.
동일 providerEventId를
3회 전달해도 실제 Business 처리 횟수는
1회여야 한다.
7. Webhook Out-of-order 테스트를 작성한다.
DELIVERED 이후 SENT 이벤트가 들어와도
상태가 SENT로 후퇴하지 않도록 한다.
8. Worker Crash를 모사할 수 있는
Integration Test를 만든다.
Job을 PROCESSING 상태로 둔 뒤
heartbeat / lease를 만료시키고
Reconciliation을 실행했을 때
안전하게 복구되는지 확인한다.
9. 외부 Provider에서는 성공했지만
DB Update 전에 Worker가 종료되는
Scenario를 테스트한다.
Reconciliation 후 SUCCESS로 복구되고
외부 요청이 다시 실행되지 않아야 한다.
10. Transaction 중간 실패 테스트를 작성한다.
예:
Order Update 성공 직후
Audit 저장 과정에서 Exception 발생
Transaction 대상이라면
전체 변경이 Rollback되어야 한다.
11. Circuit Breaker가 구현되어 있다면 다음을 테스트한다.
- CLOSED → OPEN
- OPEN 상태에서 외부 호출 차단
- OPEN → HALF_OPEN
- 성공 후 CLOSED
- 실패 후 다시 OPEN
12. 모든 Failure Scenario에서
다음 값을 검증할 수 있게 한다.
- 최종 상태
- Retry 횟수
- Side Effect 실행 횟수
- Audit Log
- Error Code
13. 실제 고객 정보나
Production Secret을 사용하지 않는다.
14. Production 환경에서는
Mock Failure Provider를 사용할 수 없도록
보호한다.
15. reliability 테스트는
기존 Unit Test와 분리해서
별도 명령으로 실행할 수 있게 한다.
예:
pnpm test:reliability
16. 가장 먼저 다음 5개 Scenario를 완성한다.
- Duplicate Notification Job
- Duplicate Webhook
- Provider Timeout
- Worker Stale Recovery
- Transaction Rollback
기존 프로젝트 구조와 구현 상태를 먼저 분석하고,
이미 존재하는 기능은 중복 구현하지 말고
현재 구조에 맞게 테스트를 추가해줘.
# Reliability GameDay
Date:
Scenario:
Environment:
## 1. Hypothesis
장애가 발생해도 무엇이 유지되어야 하는가?
## 2. Failure
어떤 장애를 발생시킬 것인가?
## 3. Expected Protection
- Timeout
- Retry
- Circuit Breaker
- Idempotency
- Reconciliation
등
## 4. Steady State
실험 전 정상 지표
## 5. Abort Condition
실험을 즉시 중단할 조건
## 6. Execution
## 7. Observations
## 8. Result
PASS / FAIL / ABORTED
## 9. Found Issues
## 10. Action Items
지금까지:
Retry
Idempotency
Reconciliation
Circuit Breaker
Bulkhead
Timeout
같은 장애 대응 구조를 설계했다.
하지만 중요한 것은:
코드가 존재한다
가 아니라
실제 장애 상황에서도
제대로 동작한다
는 것이다.
그래서 0925에서는 Failure Injection과 Chaos Testing을 다뤘다.
핵심 흐름은:
정상 상태 정의
↓
Hypothesis 작성
↓
Failure Injection
↓
관찰
↓
복구 확인
↓
Action Item
이다.
현재 프로젝트에서 가장 먼저 검증할 가치가 높은 것은:
Provider Timeout
Worker Crash
Duplicate Queue
Duplicate Webhook
Transaction Failure
다.
특히 다음 상황은 반드시 한 번 테스트할 가치가 있다.
외부 Provider에서는 성공
↓
Worker가 DB 기록 전에 종료
↓
우리 DB는 PROCESSING
↓
Reconciliation
↓
외부 상태 확인
↓
SUCCESS 복구
↓
중복 발송 없음
이 하나의 시나리오 안에:
Idempotency
Lease
Heartbeat
Reconciliation
Observability
가 모두 들어 있기 때문이다.
또한 장애 실험의 목적은:
시스템을 얼마나 심하게 망가뜨릴 수 있는가
가 아니다.
핵심은:
한 부분의 장애가
다른 핵심 기능까지 번지지 않는가?
즉 Blast Radius를 확인하는 것이다.
알림톡 Provider가 죽어도:
고객 주문은 정상 접수
되어야 하고,
Export가 폭주해도:
주문 API와 Notification Worker는 정상
이어야 한다.
그리고 장애가 끝난 뒤에도 중요하다.
Queue 10,000건
↓
Provider 정상화
↓
모든 Worker 동시 실행
처럼 복구 과정에서 다시 장애를 만드는 Recovery Storm도 방지해야 한다.
따라서 좋은 복구는:
Rate Limit
+
Concurrency Limit
+
Gradual Recovery
를 가진다.
현재 규모에서는 거대한 Chaos Engineering 플랫폼까지 만들 필요는 없다.
먼저:
Mock Provider
Reliability Integration Test
5~10개의 핵심 Failure Scenario
가끔 Staging GameDay
정도면 충분하다.
전체 운영 자동화 흐름은 이제:
Observe
↓
Measure
↓
Detect
↓
Resist
↓
Fail Safely
↓
Reconcile
↓
Recover
↓
Verify
↓
Learn
로 연결된다.
결국 장애 대응 시스템의 완성도는
“정상일 때 잘 돌아가는가”가 아니라, 일부가 망가졌을 때 어디까지 버티고 얼마나 안전하게 원래 상태로 돌아오는가
에서 드러난다.