예를 들어 NestJS 프로세스가:
PID 존재
Node.js Event Loop 동작
HTTP Port 열림
상태라고 하자.
겉으로 보면 서버는 살아 있다.
하지만 실제로:
DB 연결 실패
필수 Config 없음
Queue 연결 실패
Migration 미적용
Worker 초기화 실패
상태일 수 있다.
이 서버에 고객 요청을 보내면 실패한다.
200 OK가 아니다가장 단순한 Health Endpoint:
@Get('/health')
health() {
return { status: 'ok' };
}
는:
NestJS Controller가 응답했다
정도만 알려준다.
실제 서비스 가능 여부까지 보장하지 않는다.
대표적으로 세 가지를 구분한다.
Liveness
=
이 프로세스가 살아 있는가?
Readiness
=
지금 요청을 받아도 되는가?
Startup
=
초기화가 끝났는가?
이다.
Liveness는:
프로세스를 재시작해야 할 정도로 죽어 있는가?
를 판단한다.
정상 예:
Event Loop 정상
프로세스 정상
치명적 Deadlock 없음
이다.
예를 들어 DB가 잠깐 장애라고 하자.
Liveness가 DB를 검사해서:
DB DOWN
→ Liveness FAIL
로 판단하면 인프라가 서버를 계속 재시작할 수 있다.
하지만 서버 재시작으로 DB 장애가 해결되는 것은 아니다.
DB 장애
↓
App Liveness FAIL
↓
App Restart
↓
DB 여전히 장애
↓
Liveness FAIL
↓
Restart
가 반복된다.
일반적으로:
프로세스 자체가 정상적으로 동작 가능한가?
정도만 확인한다.
Readiness는:
이 Instance가 현재 실제 Traffic을 받아도 되는가?
를 판단한다.
여기에는 Dependency가 더 중요하다.
예:
DB 연결 가능
필수 Config 로드 완료
Application Initialization 완료
등이다.
대신:
Load Balancer에서 Traffic 제외
하는 것이 핵심이다.
즉:
프로세스는 살아 있음
하지만 새 요청은 받지 않음
상태가 가능하다.
신규 Instance가 실행됐다.
Node Process 시작
했다고 바로 Traffic을 보내면 안 된다.
아직:
Config Loading
DB 연결
Module Initialization
Cache 준비
가 끝나지 않았을 수 있다.
Startup은:
애플리케이션 초기 준비가 완료됐는가?
를 본다.
예:
필수 Env/SSM 값 로딩
DB 연결 확인
Module 초기화
중요 Schema Version 검증
이다.
Process Start
↓
STARTING
↓
초기화
↓
READY
↓
Traffic 수신
처럼 보는 것이 좋다.
예:
STARTING
READY
NOT_READY
DRAINING
STOPPING
STOPPED
정도로 볼 수 있다.
/health/live예:
GET /health/live
는 Liveness용이다.
응답:
{
"status": "ok"
}
정도면 충분할 수 있다.
/health/ready예:
GET /health/ready
는 실제 Traffic 가능 여부를 본다.
예:
{
"status": "ready"
}
또는:
{
"status": "not_ready"
}
이다.
외부 Health:
status만
내부 운영용 Health:
DB
Queue
Config
Version
Worker
상태를 더 자세히 보여준다.
나쁜 예:
{
"dbHost": "prod-rds-xxx",
"redisHost": "...",
"awsRegion": "...",
"secretVersion": "..."
}
같은 정보는 불필요하다.
예:
{
"status": "ok"
}
정도로 충분하다.
인증된 내부 경로에서는:
{
"status": "degraded",
"checks": {
"database": "ok",
"queue": "degraded"
}
}
정도로 볼 수 있다.
예:
PostgreSQL
Redis / Queue
S3
SSM
External Notification Provider
Notion
LLM Provider
등이 있다.
하지만 전부 Readiness에 넣으면 안 된다.
예:
PostgreSQL
없으면 주문/관리 기능 대부분이 동작하지 않는다.
Notion
AI Analysis Provider
Recommendation API
없어도 핵심 서비스는 동작할 수 있다.
예:
Notion DOWN
이라고 홈페이지 주문까지 Load Balancer에서 빼버리는 것은 과하다.
예:
type DependencyCriticality =
| 'CRITICAL'
| 'IMPORTANT'
| 'OPTIONAL';
정도로 나눌 수 있다.
예:
DB DOWN
이라면:
Readiness
NOT_READY
가 합리적일 수 있다.
예:
AI Provider DOWN
이면:
Readiness
READY
Service State
DEGRADED
로 둘 수 있다.
1005에서:
NORMAL
DEGRADED
OVERLOADED
상태를 다뤘다.
Health 시스템과 연결하면:
Optional Dependency FAIL
↓
DEGRADED
로 볼 수 있다.
예를 들어:
Health Check마다
DB에 INSERT
하거나:
실제 알림톡 발송
하면 안 된다.
DB는 예:
SELECT 1;
정도면 충분하다.
SELECT 1만으로 모든 DB 문제를 찾을 수 있는 것은 아니다예:
DB 연결은 됨
하지만 Connection Pool 포화
중요 Table Lock
Migration 불일치
는 못 잡을 수 있다.
Health:
지금 요청 받을 수 있는가?
Observability:
왜 느린가?
어디가 포화됐는가?
이다.
Health Endpoint 하나에 모든 진단을 넣지 않는다.
초기에는:
Connection 가능 여부
짧은 Query 성공 여부
면 충분하다.
예:
active connections
idle
waiting
등이다.
Queue 시스템 자체 연결 상태를 확인할 수 있다.
하지만:
Queue Depth = 1000
이라고 바로 Health FAIL로 볼 필요는 없다.
1005에서 다룬:
queue_depth
oldest_job_age
가 더 적합하다.
예:
Redis Queue Connection FAIL
이고 해당 Queue가 핵심 기능에 필수라면 Readiness에 영향을 줄 수 있다.
Queue가 없으면 업무 누락 위험이 크다면:
Readiness NOT_READY
를 고려한다.
1001의 Outbox가 있으면:
DB Transaction
↓
Outbox 저장
까지는 가능하다.
Queue가 잠시 내려가도 Event가 DB에 보존된다.
이게 Outbox의 운영 장점 중 하나다.
다만:
Outbox Backlog
가 너무 커지면 결국 Admission Control이 필요하다.
예:
알림톡 Provider
가 DOWN이라고 매 Health Check마다 Provider API를 호출하는 것도 문제다.
서버 10대가:
5초마다 Provider Health 호출
하면 그 자체가 불필요한 Traffic이다.
예:
최근 1분
Error Rate 80%
이면:
UNHEALTHY
로 판단한다.
직접 Ping / API 호출
실제 요청 Error/Latency 기반
이다.
DB는 Active Probe.
External Provider는 Passive Health 중심.
같은 방식이 가능하다.
Health Check 자체가 오래 걸리면 안 된다.
예:
DB Probe 5초 대기
하면 Load Balancer Health 판단도 느려진다.
예:
500ms
1s
등 보수적으로 둔다.
정확한 값은 환경에 맞춘다.
Dependency마다 매 Request 시 Health를 직접 검사하지 않는다.
예:
5초마다 Health 계산
현재 상태 Memory 저장
할 수 있다.
/health/ready는 현재 계산된 상태를 빠르게 반환Load Balancer Probe가 잦아도 DB에 매번 Query를 던지지 않게 할 수 있다.
예:
10분 Cache
는 장애 감지가 너무 느리다.
예:
5초
10초
정도다.
환경에 맞춘다.
서버를 종료할 때:
Process Kill
해버리는 것이 아니라:
새 일을 받는 것을 중단하고, 현재 진행 중인 일을 가능한 안전하게 마무리한 뒤 종료하는 것
이다.
예:
새 Version 배포
↓
Old Instance 종료
이다.
Old Instance가 요청 처리 중이라면 종료 전략이 필요하다.
SIGTERM
↓
즉시 process.exit()
하면:
HTTP 요청 중단
DB Transaction 중단
파일 생성 중단
Queue Job 중간 종료
될 수 있다.
SIGTERM 수신
↓
Readiness OFF
↓
새 요청 차단
↓
현재 HTTP 요청 Drain
↓
Worker 신규 Job Claim 중단
↓
진행 중 Job 완료/Checkpoint
↓
DB/Queue Connection 종료
↓
Process 종료
이다.
예:
READY
→
DRAINING
한다.
그러면 Load Balancer가 새 Traffic을 다른 Instance로 보낸다.
Load Balancer가 변경을 감지하는 데 시간이 걸릴 수 있다.
예:
Readiness OFF
↓
몇 초 대기
↓
HTTP Server Close 시작
같은 짧은 Drain Window를 둘 수 있다.
Load Balancer Health Check 주기와 Deregistration Delay 등을 확인해야 한다.
closeNode.js HTTP Server는 새 Connection을 받지 않고 기존 Connection이 끝나길 기다리는 방식으로 종료할 수 있다.
Client가 연결을 오래 유지할 수 있다.
따라서 최대 Shutdown Deadline이 필요하다.
예:
최대 30초
동안 기다리고 이후 강제 종료.
정확한 값은 업무 특성에 맞춘다.
Job 하나가 Bug로:
2시간
걸린다고 Deployment가 2시간 멈춰서는 안 된다.
예:
SIGTERM
T0
Graceful Deadline
T0 + 30s
처럼 둔다.
Deadline을 넘기면:
현재 상태 저장
Lease 만료 가능 상태
강제 종료
가 필요하다.
HTTP 요청은 보통 짧지만 Worker Job은:
Export
AI Report
대량 알림
Backfill
처럼 오래 걸릴 수 있다.
PAUSE CLAIM
한다.
선택:
완료까지 기다림
Checkpoint 후 중단
Lease 만료 후 다른 Worker가 Resume
즉시 Cancel
이다.
예:
Notification 한 건
1~2초
이면 완료까지 기다릴 수 있다.
예:
AI 분석
15분
이면 배포마다 15분 기다리는 것은 어렵다.
Checkpoint/Resume 구조가 필요하다.
0930과 연결된다.
Process는 죽어도 된다.
Workflow State는 DB에 남는다.
원칙이 Graceful Shutdown에서도 중요하다.
예:
COLLECT_GIT
SUCCESS
GENERATE_REPORT
SUCCESS
UPLOAD_NOTION
RUNNING
이었다면 다음 Worker가 상태를 복구한다.
예:
lockedBy
worker_123
leaseUntil
10:30
을 가진다.
예:
RUNNING
→
PENDING
또는:
RETRY_PENDING
으로 되돌린다.
예:
알림톡 API 호출
요청을 보낸 직후 Shutdown.
응답을 아직 못 받았다.
이 경우:
PENDING으로 되돌리고 다시 실행
하면 중복 발송 가능성이 있다.
RUNNING
→
UNKNOWN
또는 Reconciliation 대상 상태가 필요하다.
0916/0917과 연결된다.
외부 Side Effect는:
실제 실행 여부 확인
후 재시도한다.
예:
process.on('SIGTERM', async () => {
await worker.pause();
await worker.drain();
await shutdown();
});
같은 개념이다.
NestJS에서는 Application Shutdown Hook을 사용할 수 있다.
예:
app.enableShutdownHooks();
를 통해 종료 이벤트를 연결할 수 있다.
각 Resource가:
HTTP
DB
Queue
Worker
Scheduler
에서 어떻게 종료되는지 설계해야 한다.
좋은 예:
1. Readiness OFF
2. Scheduler Trigger 중단
3. 신규 Queue Claim 중단
4. HTTP 신규 요청 중단
5. 진행 중 작업 Drain
6. Resource Close
7. Process Exit
이다.
Shutdown 중인데 새 Job을 계속 만들면 안 된다.
Leader 역할을 수행하고 있었다면:
Scheduler Lease
를 안전하게 넘겨야 한다.
Single Scheduler 구조라면 특히 중요하다.
현재 처리 중인 요청 수를 추적할 수 있다.
예:
activeRequests
Counter를 둔다.
끝나면 -1.
Shutdown 시:
activeRequests == 0
을 기다린다.
DRAINING 상태에서도:
/health/live
는 응답 가능해야 한다.
/health/ready는 DRAINING이면 실패예:
{
"status": "not_ready",
"reason": "draining"
}
정도다.
Process 자체는 살아 있기 때문이다.
Liveness
OK
Readiness
FAIL
상태가 정상적으로 존재할 수 있다.
살아 있지만
새 Traffic은 받지 않음
이다.
Load Balancer에서 Instance가 제외된 후에도 기존 Connection이 남아 있을 수 있다.
Infrastructure Load Balancer가 기존 Request를 처리하도록 시간을 주는 설정이 있을 수 있다.
App Shutdown Timeout과 맞춰야 한다.
App이 먼저 죽어 기존 Request가 끊길 수 있다.
배포 시스템이 강제로 Kill할 수 있다.
Load Balancer
Deployment System
Container/EC2 Process
Application
Worker
설정을 맞춘다.
일반적으로:
SIGTERM
=
정상 종료 요청
SIGKILL
=
즉시 강제 종료
로 생각하면 된다.
따라서 SIGTERM 단계에서 최대한 상태를 안전하게 만든다.
예:
Ctrl + C
이다.
여러 Signal이 들어와도:
cleanup
두 번 실행
되어 문제 생기지 않게 한다.
if (isShuttingDown) {
return;
}
isShuttingDown = true;
같은 Guard를 둔다.
예:
POST /automation/run
요청이 Drain 시작 후 들어오면:
503
등으로 거절할 수 있다.
Worker 종료 Deadline이 오기 전에 Checkpoint를 남긴다.
예:
Ollama Generation
이 10분 걸리는 상황.
선택:
Generation 완료 기다림
Generation Cancel
Artifact Checkpoint 후 Retry
가 있다.
LLM 생성 자체를 중간 Token부터 Resume하기는 어렵다.
따라서:
LLM Step
자체는 다시 실행될 수 있다.
예:
Git Context 수집
SUCCESS
Context Artifact
저장
↓
LLM Step만 재실행
한다.
예:
RUNNING
→
INTERRUPTED
또는:
RETRY_PENDING
을 사용할 수 있다.
Request 전송 후 응답을 못 받고 종료됐다면 Provider 비용은 이미 발생했을 수 있다.
Retry 시 중복 비용 가능성을 기록한다.
대형 Excel 파일 생성 중이라면:
임시 파일
이 남을 수 있다.
예:
BUILDING
COMPLETE
ABORTED
를 둘 수 있다.
1003의 Retention/Cleanup과 연결된다.
미완료 Upload가 남을 수 있다.
이런 Temporary Resource Cleanup도 고려한다.
보통 Process가 종료되면 Connection이 끊기고 Transaction은 Rollback될 수 있다.
하지만 이것만 믿지 않는다.
Shutdown과 무관하게:
짧은 Transaction
이 운영에 유리하다.
Schema Migration 도중 Process가 죽으면 특히 위험하다.
Production Application 시작할 때:
매번 자동 Migration 실행
하는 구조는 신중해야 한다.
Migration Runner를 별도로 관리하거나 Lock이 필요하다.
1002의 Compatibility와 연결된다.
예:
Application requiredSchemaVersion
2026100601
DB가 아직:
2026100503
이면 Readiness를 열지 않는다.
DB Migration은 Roll Forward가 더 안전한 경우가 많다.
예:
Config
Logger
DB
Queue
Cache
Scheduler
순서로 초기화할 수 있다.
예:
Notion
연결 실패 때문에 전체 Application Startup을 막지 않는다.
실제로 기능이 호출될 때 외부 Client를 초기화한다.
Trade-off다.
예:
PostgreSQL
은 미리 확인하는 편이 좋다.
1004 원칙대로 Cache가 없어도 DB fallback 가능하다면:
Redis Cache DOWN
이 전체 Readiness FAIL 이유가 아닐 수 있다.
같은 Redis가 핵심 Queue를 담당한다면 영향이 더 크다.
Cache
Queue
Session
중 무엇을 담당하는지 봐야 한다.
실제 역할 기준으로 판단한다.
예:
database
CRITICAL
OK
queue
IMPORTANT
OK
cache
OPTIONAL
DOWN
notion
OPTIONAL
DOWN
이면 전체:
DEGRADED
지만 Readiness는 READY일 수 있다.
예:
HEALTHY
DEGRADED
UNHEALTHY
로 나눌 수 있다.
Health
DEGRADED
Readiness
READY
가 가능하다.
예:
서비스
DEGRADED
고객 주문
정상
AI Report
장애
처럼 표현한다.
예:
READY
NOT_READY
READY
NOT_READY
가 초 단위로 반복되면 Load Balancer가 Traffic을 계속 넣고 뺄 수 있다.
이런 상태를 Health Flapping이라고 볼 수 있다.
한 번 Probe 실패했다고 바로 NOT_READY로 바꾸지 않을 수 있다.
예:
연속 3회 실패
→ NOT_READY
이다.
정상 Probe도:
연속 2회 성공
→ READY
처럼 둘 수 있다.
1005에서 Overload 상태 전환과 비슷하다.
Threshold는 Dependency 특성에 맞춘다.
예:
lastSuccessAt
lastFailureAt
consecutiveFailures
을 관리하면 좋다.
내부 Monitoring에서 사용한다.
예:
health_check_total
health_check_failure_total
readiness_state
dependency_health_state
등이다.
ready → not_ready
횟수가 갑자기 늘면 문제를 알 수 있다.
예:
shutdown_started_total
graceful_shutdown_success_total
forced_shutdown_total
shutdown_duration
정도다.
예:
Worker Job이 너무 오래 걸림
Shutdown Deadline 부족
Drain 실패
일 수 있다.
http_active_requests
를 보면 Drain 진행 상태를 알 수 있다.
worker_active_jobs
도 중요하다.
예:
DRAINING
HTTP Active
3
Worker Active
1
Deadline Remaining
12s
같은 내부 상태를 볼 수 있다.
DRAINING 상태에서는:
일반 API
503
로 거절하거나 Load Balancer에서 애초에 전달되지 않게 한다.
이게 Graceful Drain의 기본이다.
연결이 오래 지속될 수 있기 때문이다.
예:
server_maintenance
Event를 보내고 Client가 Reconnect하도록 한다.
단 지금 실제 WebSocket이 없다면 미리 과도하게 구현할 필요는 없다.
Shutdown 시 Reconnect 전략이 필요하다.
모든 Client가 동시에 재연결하면 Restart 직후 Traffic Spike가 생긴다.
Load Balancer/Server가 기존 Connection은 유지하되 신규 Connection을 다른 Instance로 보내는 구조다.
New Instance READY
↓
Traffic Shift
↓
Old Instance DRAINING
↓
Old Instance STOP
이다.
예:
1. New Version 시작
2. Startup 완료
3. Readiness PASS
4. Traffic 일부 전환
5. Health/SLI 확인
6. Old Version Readiness OFF
7. Old Version Drain
8. Old Version 종료
이다.
0927 Release Safety에서 다룬 배포 구조의 기반이다.
예:
Process 시작
→ READY
를 너무 빨리 내면 초기화 중 Traffic을 받는다.
Optional Dependency 하나 때문에 신규 Instance가 영원히 Traffic을 못 받을 수 있다.
App Initialized
AND
DB Healthy
AND
Required Config Loaded
AND
Schema Compatible
이면 READY.
Cache
Notion
AI
등이다.
필수 Config가 없는 서버가 시작되는 것을 막는다.
예:
JWT Secret
DB URL
필수 AWS Parameter
등이다.
configured: true
정도만 본다.
예:
필수 값 없음
→ Process Start 실패
가 더 낫다.
Fail Fast 원칙이다.
Startup:
필수 설정 없음
Runtime:
외부 Provider 잠시 장애
이다.
Deployment가 문제를 감지한다.
전체 서버를 계속 재시작하지 않는다.
Health는 확인만 한다.
변경 작업을 하지 않는다.
이 원칙이 중요하다.
읽기
검증
상태 확인
만 한다.
Shutdown 중 Request가 끊겨 Client가 Retry할 수 있다.
예:
POST /orders
응답 직전에 서버 종료.
Client가 재시도하면 중복 주문 위험이 있다.
0916과 직접 연결된다.
Network는 언제든 끊길 수 있다.
Graceful Shutdown
+
Idempotency
+
Reconciliation
이 함께 필요하다.
Shutdown을 아무리 잘 해도 서버 전원 장애는 발생할 수 있다.
따라서:
Lease
Checkpoint
Idempotency
가 최종 안전장치다.
중요 원칙이다.
대규모 Migration 등으로 일부 기능을 잠시 막아야 할 수 있다.
예:
전체 고객 주문
OFF
보다:
관리자 Export
OFF
만 필요한 경우가 많다.
예:
order_write_enabled
export_enabled
ai_enabled
같은 Feature Flag와 연결한다.
DB Maintenance 때:
조회 가능
수정 불가
모드가 유용할 수 있다.
Product Read
OK
Order Create
503 / Maintenance
형태다.
Frontend 버튼만 숨기면 안 된다.
Backend에서 Write를 거절해야 한다.
예:
503 Service Unavailable
와 명확한 Error Code:
SERVICE_MAINTENANCE
를 사용할 수 있다.
예정된 Maintenance라면 Retry-After나 메시지를 제공할 수 있다.
Maintenance 동안:
Cron Job
이 계속 실행되면 문제일 수 있다.
예:
Report
Cleanup
Backfill
등 Low Priority Job을 일시 중지한다.
모든 Scheduler를 무조건 끄지 않는다.
1003 Cleanup이 실행 중인데 Deployment가 시작되면:
현재 Batch 완료
Checkpoint
중단
정도로 처리할 수 있다.
CutoffAt과 Cursor가 고정되어 있으므로 이어갈 수 있다.
1002 Migration Backfill:
lastCursor
processedCount
를 저장하면 Resume 가능하다.
장기 Worker가 Web API와 같은 Process에 있으면:
API Deploy
→ Worker도 종료
된다.
예:
api
worker
scheduler
를 별도 Process로 운영하면 배포 영향 범위를 줄일 수 있다.
먼저 같은 코드베이스라도:
PROCESS_ROLE=api
PROCESS_ROLE=worker
정도로 분리할 수 있다.
예:
API
WORKER
SCHEDULER
로 나눈다.
API:
DB ready?
HTTP ready?
Worker:
Queue ready?
DB ready?
Scheduler:
DB?
Leader Lock?
등이다.
운영 Probe용 작은 Health Server만 열 수 있다.
단순 Process가 살아 있는 것뿐 아니라:
lastTickAt
lastSuccessfulRun
등을 확인할 수 있다.
Process는 살아 있는데:
lastJobProcessedAt
3시간 전
일 수 있다.
자동 재시작보다는 Alert가 적합할 수 있다.
예:
worker_last_success_timestamp
scheduler_last_tick_timestamp
를 둔다.
새벽에 Job이 없을 수 있으므로 단순:
10분 Job 처리 없음
→ 장애
라고 판단하면 안 된다.
이 조합이 더 의미 있다.
queue_depth > 0
AND
worker_active = 0
AND
lastProcessed 오래됨
이면 문제 가능성이 높다.
단일 Metric 하나로 판단하지 않는다.
예:
HTTP Keep-Alive
무한 Retry Job
External API Hang
DB Query Hang
Worker Cancel 미지원
등이다.
0924/1005와 연결된다.
Shutdown Deadline보다 긴 Network Timeout이 있으면 종료가 늦어진다.
Shutdown Deadline
30s
External API Timeout
120s
는 좋지 않다.
Client가 연결을 끊었을 때 Backend가 불필요한 작업을 계속할 수 있다.
지원 가능한 작업에서는:
Client disconnected
↓
External request cancel
할 수 있다.
업무 특성에 따라 다르다.
Write는 상태 일관성이 더 중요하다.
예:
Critical Write
완료 우선
Read Request
종료 가능
Low Priority AI
Checkpoint 후 중단
처럼 나눌 수 있다.
예:
현재 Job 실패
shutdown 진행 중
이라면 같은 Process에서 새 Retry Attempt를 시작하지 않고 Queue로 넘긴다.
Parent가 끝나기 전에 Process가 죽을 수 있다.
Outbox나 Transactional Job 생성 구조를 활용한다.
예:
shutdownService.isDraining()
으로 여러 Module이 확인할 수 있다.
Middleware/Worker/Scheduler Entry Point에서 처리하는 편이 낫다.
예:
ShutdownCoordinator
├─ HTTP
├─ Scheduler
├─ Worker
├─ Queue
├─ DB
└─ Cache
순서대로 종료를 조정한다.
예:
interface ManagedResource {
stopAcceptingWork(): Promise<void>;
drain(): Promise<void>;
close(): Promise<void>;
}
정도로 추상화할 수 있다.
처음에는 명시적 Shutdown Service 하나면 충분하다.
예:
shutdown.started
readiness.disabled
scheduler.paused
worker.claim.paused
http.draining
worker.drained
db.closed
shutdown.completed
를 남긴다.
대신:
instanceId
releaseId
가 중요하다.
예:
{
"event": "shutdown.started",
"instanceId": "api-2",
"releaseId": "release_20261006_01"
}
정도다.
예:
DEPLOYMENT
MANUAL
CRASH
AUTOSCALING
HEALTH_RESTART
등이다.
Crash에는 Graceful Hook이 실행되지 않을 수 있다.
예:
stale lease
RUNNING job
unfinished workflow
를 Reconciler가 복구한다.
Worker Instance가:
lastHeartbeatAt
을 기록하면 죽은 Worker가 잡고 있던 Job을 찾기 쉽다.
예:
1초마다 모든 Worker DB UPDATE
는 과할 수 있다.
업무 SLA에 맞춘다.
예:
workerId
startedAt
lastHeartbeatAt
status
를 저장할 수도 있다.
Provider Circuit이 OPEN이면:
provider health
UNHEALTHY
로 볼 수 있다.
Optional Provider라면 전체 Traffic을 막지 않는다.
예:
Kakao Provider
CIRCUIT OPEN
AI Provider
HEALTHY
같이 본다.
Health Probe가 일반 API Rate Limit에 걸리면 안 된다.
하지만 Public 인터넷에 무제한 노출할 필요도 없다.
환경에 맞춘다.
외부 Probe는 보통 단순 Endpoint가 필요할 수 있다.
Detailed Diagnostics는 인증된 Internal Endpoint.
이 분리가 현실적이다.
Health Response가 CDN/Browser에 Cache되면 안 된다.
따라서:
Cache-Control: no-store
같은 정책을 고려한다.
앞서 말한 내부 상태 Cache를 이용할 수 있다.
현재 작은 규모에서는 큰 문제는 아니지만 확장 시 고려한다.
실제 사용자 흐름을 가볍게 테스트하는 Synthetic Check도 있다.
예:
상품 목록 API
로그인 없이 조회
를 외부 Monitoring이 주기적으로 요청한다.
Liveness:
프로세스 살았나?
Readiness:
Traffic 받아도 되나?
Synthetic:
사용자 관점 기능이 실제 동작하나?
이다.
실제 주문 데이터가 생길 수 있다.
Test 전용 경로나 Staging에서 수행한다.
예:
메인 페이지
상품 목록
상품 상세
이다.
예:
DB 연결 OK
App READY
하지만 Product Query Bug
500
이다.
Process
Dependency
User Journey
다.
실제로 배포 전에 테스트해보는 것이 좋다.
시나리오:
요청 처리 중 SIGTERM
이다.
신규 요청 차단
기존 요청 완료
응답 유실 없음
Process 정상 종료
이다.
Job 실행 중 SIGTERM
Expected:
새 Job Claim 중단
진행 중 Job 처리 정책 준수
Lease/상태 정상
다른 Worker Resume 가능
이다.
예:
Provider 응답 대기 중 SIGTERM
Expected:
중복 Side Effect 없음
UNKNOWN/Reconciliation 처리
이다.
일부 Job을 의도적으로 Hang시킨다.
Expected:
Deadline 이후 강제 종료
stale lease 복구 가능
이다.
DB Probe를 간헐적으로 실패시킨다.
Expected:
1회 실패로 Traffic 계속 출렁이지 않음
이다.
예:
Notion DOWN
Expected:
App READY
AI/Report 기능만 DEGRADED
이다.
DB DOWN
Expected:
Readiness FAIL
Liveness는 상황에 따라 OK
이다.
Expected:
연속 Health 성공
→ READY 복귀
이다.
Staging에서:
Traffic 지속
↓
New Release 배포
중 Error Rate를 본다.
예:
5xx Spike 없음
Connection Reset 없음
중복 주문 없음
Queue Job 유실 없음
이다.
stuck jobs 없음
duplicate side effect 없음
lease recovery 정상
이다.
예:
Application
HEALTHY
DB
HEALTHY
Queue
HEALTHY
Cache
DEGRADED
Notification Provider
HEALTHY
AI
DOWN
처럼 볼 수 있다.
예:
Dependency
Status
Last Checked
Last Success
Error Code
Criticality
정도다.
운영자용 메시지와 개발자 Detail을 나눌 수 있다.
예:
DB_TIMEOUT
QUEUE_UNAVAILABLE
CACHE_DEGRADED
PROVIDER_CIRCUIT_OPEN
이다.
최근:
READY → NOT_READY
전환 이력을 남길 수 있다.
Incident 분석에 도움된다.
Metric/Monitoring 시스템에 남기는 편이 낫다.
상태 전환만 Event로 기록할 수 있다.
예:
application.readiness.changed
dependency.health.changed
를 남길 수 있다.
상태 변화 시점만 기록한다.
예:
DB NOT_READY
5분 지속
이면 Incident Candidate가 될 수 있다.
실제 고객 영향에 따라 Severity를 결정한다.
0923과 연결된다.
단독으로 Incident를 결정하지 않는다.
예:
최근 30분 Dependency Health
Readiness 전환
Queue Lag
Error Rate
를 요약해:
DB Connection 문제 가능성
후보를 제안할 수 있다.
Health Failure 원인이 외부 DB인데 App Restart는 효과가 없을 수 있다.
예:
Process Hang
Event Loop 완전 정지
같은 상황이다.
이 차이를 다시 기억한다.
Liveness FAIL
→ Restart 후보
Readiness FAIL
→ Traffic 제외
이다.
Degraded Mode
후 해당 기능만 제한한다.
PROCESS DEAD
→ Restart
INSTANCE NOT READY
→ Remove From Traffic
OPTIONAL FEATURE DOWN
→ Degrade Feature
이다.
/health/live
/health/ready
분리.
Readiness 조건:
Application Initialized
DB Connected
Required Config Valid
정도다.
SIGTERM Handler
Readiness OFF
HTTP Drain
을 구현한다.
Queue Worker:
Pause Claim
Active Job Drain
Shutdown Deadline
을 구현한다.
긴 Workflow에:
Checkpoint
Lease
Resume
를 적용한다.
Optional Dependency:
Cache
AI
Notion
Notification Provider
를 Degraded 상태로 분리한다.
Staging에서:
SIGTERM
DB Failure
Redis Failure
Provider Failure
Reliability Test를 수행한다.
Service Mesh Health Routing
복잡한 Multi-region Failover
다중 Cluster Drain Controller
Custom Orchestrator
매 Dependency마다 별도 Health DB
까지 필요 없다.
실제 장기 Job 때문에 배포 영향이 커질 때 Worker Process를 분리한다.
src/
├─ health/
│ ├─ health.controller.ts
│ ├─ health.service.ts
│ ├─ readiness.service.ts
│ ├─ dependency-health.service.ts
│ └─ health.types.ts
│
├─ lifecycle/
│ ├─ shutdown.service.ts
│ ├─ drain.service.ts
│ └─ process-signal.service.ts
│
├─ workers/
└─ scheduler/
예:
type ReadinessState =
| 'STARTING'
| 'READY'
| 'DRAINING'
| 'NOT_READY';
이다.
class ReadinessService {
private state:
ReadinessState = 'STARTING';
markReady() {
this.state = 'READY';
}
markDraining() {
this.state = 'DRAINING';
}
isReady() {
return this.state === 'READY';
}
}
정도로 시작할 수 있다.
interface DependencyHealth {
name: string;
status:
| 'HEALTHY'
| 'DEGRADED'
| 'UNHEALTHY';
criticality:
| 'CRITICAL'
| 'IMPORTANT'
| 'OPTIONAL';
checkedAt: Date;
}
예:
CRITICAL Dependency
UNHEALTHY
→ NOT_READY
OPTIONAL Dependency
UNHEALTHY
→ DEGRADED
이다.
class ShutdownService {
private shuttingDown = false;
async beginShutdown() {
if (this.shuttingDown) {
return;
}
this.shuttingDown = true;
// readiness off
// scheduler pause
// workers pause
// drain
// close resources
}
}
정도로 볼 수 있다.
Cleanup 하나가 실패했다고:
Shutdown 전체 무한 대기
하지 않는다.
Cache Close 실패
는 Log 남기고 다음 단계로 진행할 수 있다.
결국 Process는 종료돼야 한다.
예:
Scheduler Pause
2s
Worker Drain
20s
DB Close
5s
같이 둘 수 있다.
Step Timeout 합이 전체 Deadline을 넘지 않게 한다.
Shutdown은 운영 로그 성격이 강하다.
예:
APP_STARTING
APP_DRAINING
DATABASE_UNAVAILABLE
SCHEMA_INCOMPATIBLE
CONFIG_INVALID
등이다.
Internal Diagnostics에서만 상세 Error Code를 본다.
예:
503
을 사용할 수 있다.
200
Readiness FAIL:
503
형태가 흔하다.
항상 200 안에서 JSON status만 바꾸면 Load Balancer가 실패를 감지하지 못할 수 있다.
CloudFront 같은 앞단 Cache는 정적 응답을 계속 제공할 수 있다.
Backend Drain과 별개다.
Backend Instance들이 모두 NOT_READY라면 Load Balancer는 503을 반환할 수 있다.
이게 무중단 배포 기본이다.
Old Process를 내리기 전에 New Process를 병렬로 띄워야 한다.
예:
Port 3000
Old
Port 3001
New
Nginx Upstream을 전환하는 방식이다.
Infrastructure를 크게 바꾸지 않는다.
Nginx가 새 요청을 Old Process로 보내지 않도록 설정하는 방식도 고려할 수 있다.
PM2, systemd, Docker, ECS 등 방식에 따라 다르다.
신규 Release 후:
Readiness PASS
만 보고 바로 100% Traffic을 보내지 않을 수 있다.
예:
Error Rate
p95
DB Error
Queue Error
를 잠깐 확인하고 Traffic을 확대한다.
Readiness
= 요청 받을 기본 자격
Canary Metric
= 실제 새 Release 품질
이다.
코드 Bug는 실제 요청에서만 나타날 수 있다.
예:
app_startup_duration
을 측정할 수 있다.
의존성 증가
초기 대량 Query
잘못된 Cache Warm-up
등을 의심할 수 있다.
1004와 연결된다.
핵심 데이터만 준비하거나 Lazy Load한다.
Critical:
Config
DB
Schema Compatibility
Background:
일부 Cache Warm-up
AI Model Metadata
Report Preload
이다.
해당 기능만 Degraded.
Health Endpoint에서:
DB Connection String
Version 상세 Secret
Token
등은 절대 반환하지 않는다.
Internal Endpoint에서는 유용하지만 Public Endpoint에는 최소화할 수 있다.
예:
Release
20261006-01
Commit
abc1234
는 Incident 분석에 유용하다.
여러 서버면:
api-1
api-2
처럼 Instance별 Health를 본다.
전체 서비스 Health와 Instance Health를 구분한다.
예:
AI Provider A
DOWN
Provider B
available
라면 Fallback이 가능할 수 있다.
Provider Adapter/Resilience Layer 역할이다.
Resilience Layer가:
Retry
Fallback
Circuit Breaker
를 결정한다.
Health
관찰
Resilience
대응
Lifecycle
종료/시작
이다.
예:
Ollama Process
Model Availability
를 확인할 수 있다.
AI Workflow만:
DEGRADED
로 본다.
매 Health Check마다 실제 긴 LLM Generation을 하지 않는다.
Ollama API reachable
Model listed
정도면 충분하다.
예:
Ollama available
Required model available
Work directory accessible
등이다.
AI Worker가 특정 Repository 작업 전용이라면:
repo accessible
정도다.
지원 범위를 봐야 한다.
Queue Consumer를 Pause한다.
짧은 명령은 완료.
장시간 명령은 Timeout/Cancel 정책을 따른다.
예:
commit
checkout
rebase
중간 종료는 Repository 상태를 꼬이게 할 수 있다.
완료 여부를 확인한 뒤 Step을 SUCCESS 처리한다.
다음 Run에서:
git status
HEAD
lock file
working tree
를 확인한다.
현재 NestJS + Prisma + PostgreSQL + Queue/Worker 구조를 기준으로
Health Check, Readiness/Liveness, Graceful Shutdown 구조를 추가해줘.
목표는 Kubernetes 수준의 복잡한 플랫폼을 만드는 것이 아니라,
- 신규 Instance가 준비되기 전에 Traffic을 받지 않게 하고
- 배포/재시작 시 신규 요청과 Job을 먼저 차단하고
- 진행 중 작업을 가능한 안전하게 Drain한 뒤
- Process가 죽더라도 Job/Workflow를 복구할 수 있게 만드는 것이다.
현재 배포 방식, Process Manager, Queue Library,
NestJS Bootstrap 구조를 먼저 분석한다.
특정 Infrastructure를 추정해서 구현하지 말고
현재 프로젝트에 맞춘다.
1. Health Endpoint를 최소 두 개로 분리한다.
GET /health/live
목적:
프로세스 자체가 살아 있는지 확인.
GET /health/ready
목적:
현재 Instance가 실제 Traffic을 받을 준비가 됐는지 확인.
2. Liveness에는
DB/외부 Provider의 일시 장애를 과도하게 포함하지 않는다.
외부 Dependency 장애 때문에
Application이 무한 Restart Loop에 빠지지 않게 한다.
3. Readiness에는 우선 다음을 고려한다.
- application initialization 완료
- PostgreSQL 연결 가능
- required config validation 완료
- 필요한 경우 DB schema compatibility 확인
4. Optional Dependency를 분리한다.
예:
- Cache
- AI Provider / Ollama
- Notion
- Notification Provider
Optional Dependency 장애 때문에
핵심 웹 서비스 전체 Readiness를
무조건 FAIL시키지 않는다.
5. Dependency마다 Criticality를 둘 수 있는
간단한 구조를 만든다.
- CRITICAL
- IMPORTANT
- OPTIONAL
6. 전체 Service Health는 다음 정도로 표현할 수 있게 한다.
- HEALTHY
- DEGRADED
- UNHEALTHY
Readiness와 Health Status를 동일 개념으로 만들지 않는다.
예:
AI DOWN
→ service DEGRADED
→ readiness READY
7. Health Check에는 Side Effect를 넣지 않는다.
금지:
- DB INSERT
- 실제 알림 발송
- Production Write
- Migration 실행
8. DB Probe는 가벼운 방식으로 구현한다.
9. Health Check 자체에 짧은 Timeout을 둔다.
Health Probe 때문에 Application Resource가 오래 묶이지 않게 한다.
10. Public Health Response에는
민감한 Infrastructure 정보를 노출하지 않는다.
11. 상세 Dependency 상태가 필요하다면
인증된 Internal Health Endpoint 또는
운영 Dashboard용 Service로 분리한다.
12. Readiness 상태를 Application Lifecycle과 연결한다.
예:
- STARTING
- READY
- DRAINING
- NOT_READY
13. Application Startup 완료 전에
READY가 되지 않게 한다.
14. Graceful Shutdown을 구현한다.
SIGTERM 수신 시 최소 순서:
1) 중복 Shutdown 방지
2) Readiness → DRAINING
3) Scheduler 신규 Trigger 중단
4) Worker 신규 Job Claim 중단
5) HTTP 신규 요청 Drain
6) 진행 중 Job 처리
7) Queue/DB/기타 Resource Close
8) Process 종료
15. process.exit()를 Signal 직후 바로 호출하지 않는다.
16. Shutdown 전체에 Deadline을 둔다.
Deadline 이후에는
무한 대기하지 않고 강제 종료가 가능해야 한다.
정확한 시간은 Config로 조정 가능하게 한다.
17. 각 Resource Close 작업도
무한 대기하지 않게 한다.
18. HTTP Active Request 수를
추적할 수 있는 간단한 구조를 고려한다.
Shutdown 시 가능한 경우
현재 요청이 완료될 때까지 기다린다.
19. DRAINING 상태에서는
/health/ready가 실패하도록 한다.
/health/live는 Process가 살아 있는 동안
정상일 수 있다.
20. Worker는 Shutdown 시작 후
새 Job을 Claim하지 않는다.
21. 짧은 Job은 완료까지 기다릴 수 있게 한다.
22. 장시간 Job은
기존 Checkpoint / WorkflowRun / Lease 구조가 있다면
이를 이용해 Resume 가능하게 한다.
새로운 중복 Workflow Engine을 만들지 않는다.
23. External Side Effect 요청 중 Shutdown이 발생해
결과가 불확실하다면
무조건 PENDING으로 되돌려 Blind Retry하지 않는다.
UNKNOWN / Reconciliation 흐름과 연결한다.
24. Worker Claim에 Lease/Heartbeat 구조가 있다면
Shutdown 시 안전하게 Release하거나
Lease 만료 후 다른 Worker가 복구할 수 있게 한다.
25. Scheduler가 있다면
Shutdown 시 신규 ScheduleRun 생성을 중지한다.
Leader Lock/Lease 구조가 있다면
다른 Instance가 이어받을 수 있게 한다.
26. Queue / Redis가 Cache와 Worker에
서로 다른 역할을 한다면
Health Criticality를 역할별로 판단한다.
27. Cache 장애 시 DB fallback이 가능한 경우
Cache 장애만으로 전체 Readiness를 FAIL시키지 않는다.
28. Outbox 구조가 있다면
Queue 일시 장애 상황에서
핵심 DB Write를 계속 받을 수 있는지 현재 구조를 분석한다.
무조건 허용하지 말고
Outbox backlog 한도와 업무 위험을 같이 고려한다.
29. Optional Provider Health는
매 Health Probe마다 실제 외부 API를 호출하지 않는다.
기존 실제 호출 Error Rate / Circuit Breaker 상태 /
주기적 경량 Probe를 활용할 수 있게 한다.
30. Health Flapping을 줄일 수 있게 한다.
필요하면:
- consecutive failure count
- consecutive success count
를 이용한다.
31. Dependency Health에 다음 Metadata를
내부적으로 유지할 수 있게 한다.
- status
- lastCheckedAt
- lastSuccessAt
- lastFailureAt
- errorCode
32. Structured Log Event를 추가한다.
예:
- application.starting
- application.ready
- readiness.disabled
- shutdown.started
- worker.claim.paused
- worker.draining
- shutdown.completed
- shutdown.forced
- dependency.health.changed
33. Log에는 가능하면:
- instanceId
- releaseId
- processRole
을 연결한다.
34. Health Probe 결과를 매번 DB에 저장하지 않는다.
Metric 또는 상태 변화 Event 중심으로 기록한다.
35. 다음 Metric을 고려한다.
- readiness_state
- dependency_health_state
- http_active_requests
- worker_active_jobs
- shutdown_duration
- forced_shutdown_total
36. API / Worker / Scheduler를 현재 하나의 Process가 담당하고 있다면
바로 Infrastructure를 세 개로 분리하지 않는다.
다만 코드에서 Process Role을
향후 분리하기 쉬운 구조로 만드는 것은 고려한다.
37. 다음 Reliability Test를 작성한다.
- 정상 Startup → READY
- 필수 Config 누락
- DB unavailable
- Optional Notion unavailable
- Cache unavailable
- Readiness DRAINING 상태
- HTTP 요청 처리 중 SIGTERM
- Worker Job 실행 중 SIGTERM
- External API 호출 중 SIGTERM
- Shutdown Deadline 초과
- Health Probe 간헐적 실패
- DB 복구 후 Readiness 회복
- 동일 Shutdown Signal 여러 번 수신
38. 테스트에서 확인한다.
- 신규 요청 차단
- 진행 중 요청 완료 여부
- 중복 Side Effect 여부
- Worker Job 유실 여부
- Lease/Checkpoint 복구
- Readiness 상태
- Structured Log
39. 실제 Production Process를 강제 종료하지 말고
Integration/Staging 환경에서 재현한다.
40. 현재 프로젝트 규모에 맞게
최소 구조로 구현하고,
Service Mesh, Kubernetes Operator,
복잡한 Custom Orchestrator 같은
불필요한 구조는 추가하지 않는다.
현재 서버의 Graceful Shutdown 구현을 분석해줘.
코드를 바로 수정하지 말고 먼저 다음을 확인한다.
1. SIGTERM 처리 여부
2. SIGINT 처리 여부
3. NestJS enableShutdownHooks 사용 여부
4. HTTP Server Close 여부
5. Prisma Connection 종료 여부
6. Queue Worker Pause 여부
7. Scheduler Pause 여부
8. 실행 중 Job 처리 방식
9. Shutdown Timeout 여부
10. process.exit 직접 호출 위치
그 후 다음 위험을 찾아줘.
P0:
- Signal 직후 즉시 process.exit
- DB Write 중 강제 종료 가능
- 동일 Job 중복 실행 가능
- External Side Effect Blind Retry 가능
P1:
- Worker 신규 Job Claim 계속됨
- Scheduler가 종료 중 신규 Job 생성
- Readiness가 Drain 시작 후에도 READY
- Shutdown Timeout 없음
P2:
- Structured Log 부족
- Shutdown Metric 없음
- Optional Resource Close 순서 개선 가능
마지막에 현재 구조를 최대한 유지하면서
수정 범위를 최소화한 개선안을 제시해줘.
현재 프로젝트의 Health Check와 Dependency를 분석해서
Liveness / Readiness / Optional Health를 구분해줘.
코드는 수정하지 않는다.
검토 Dependency:
- PostgreSQL
- Redis / Queue
- Cache
- AWS 관련 필수 Resource
- Notification Provider
- Notion
- AI / Ollama
- Scheduler
- Worker
각 Dependency에 대해:
## Dependency
## 현재 사용 용도
## Criticality
- CRITICAL
- IMPORTANT
- OPTIONAL
## Liveness 포함 여부
## Readiness 포함 여부
## Degraded Mode 가능 여부
## 권장 Probe
- Active
- Passive
- Metric 기반
## Probe 비용
## Timeout 필요 여부
## 장애 시 전체 서비스 영향
를 정리한다.
특히 Optional Dependency 하나가 장애라고
전체 Application Restart를 유발하지 않게 한다.
마지막에:
1. /health/live에 포함할 것
2. /health/ready에 포함할 것
3. Dashboard만 표시할 것
세 그룹으로 나눠줘.
현재 배포 구조에서
Zero-Downtime 관점의 검증 계획을 작성해줘.
목표:
새 Release 배포 중에도
- 진행 중 Request 유실 없음
- 신규 Request 5xx 최소화
- Worker Job 유실 없음
- 중복 Side Effect 없음
을 확인한다.
Scenario:
1. 평상시 Traffic 발생
2. New Instance/Process 시작
3. Startup 완료 전 Traffic 유입 여부 확인
4. Readiness PASS
5. Traffic 전환
6. Old Instance DRAINING
7. 진행 중 HTTP Request 완료
8. Worker 신규 Claim 중지
9. Worker 진행 중 Job 처리
10. Old Instance 종료
관찰 Metric:
- HTTP 5xx
- connection reset
- p95 latency
- active requests
- queue depth
- active jobs
- duplicate job execution
- shutdown duration
실제 Production Traffic이 아니라
Staging/Test Traffic으로 검증한다.
1005에서는:
시스템이 얼마나 많은 일을 받을 것인가?
를 다뤘다.
1006에서는 그 다음 질문인:
이 서버가 지금
새 일을 받을 준비가 되어 있는가?
를 다뤘다.
가장 먼저:
Liveness
Readiness
Startup
을 구분해야 한다.
Liveness는:
이 Process를 재시작해야 하는가?
를 본다.
Readiness는:
이 Instance에 지금 Traffic을 보내도 되는가?
를 본다.
그래서:
Liveness OK
Readiness FAIL
상태는 이상한 것이 아니다.
배포 중 Old Instance가 Drain될 때 오히려 정상적인 상태다.
또 모든 Dependency 장애를 같은 수준으로 처리해서는 안 된다.
예를 들어:
PostgreSQL DOWN
은 핵심 기능을 막을 수 있지만:
Notion DOWN
AI DOWN
은 핵심 주문 서비스 전체를 죽일 필요가 없을 수 있다.
따라서:
CRITICAL
IMPORTANT
OPTIONAL
으로 Dependency를 분류하고,
Optional 장애
→ DEGRADED
Critical 장애
→ NOT_READY
처럼 처리한다.
Graceful Shutdown의 핵심 흐름은:
SIGTERM
↓
Readiness OFF
↓
Scheduler Pause
↓
Worker Claim Pause
↓
HTTP Drain
↓
진행 중 Job 처리
↓
Resource Close
↓
Process Exit
이다.
중요한 점은:
종료 요청을 받았다고 바로 Process를 죽이지 않는 것
이다.
하지만 Graceful Shutdown도 완벽한 방어는 아니다.
서버 전원 장애나 SIGKILL처럼 Cleanup 기회조차 없는 상황이 존재한다.
그래서 Worker에는 여전히:
Idempotency
Lease
Heartbeat
Checkpoint
Reconciliation
이 필요하다.
즉:
Graceful Shutdown
은 복구 구조를 대체하는 것이 아니라,
복구가 필요한 상황 자체를 줄여주는 추가 안전장치
다.
장시간 Workflow에서는 특히:
Process는 죽어도 된다.
Workflow State는 살아 있어야 한다.
원칙이 중요하다.
API 배포와 연결하면:
New Process STARTING
↓
Initialization
↓
READY
↓
Traffic 전환
↓
Old Process DRAINING
↓
기존 요청 완료
↓
STOP
이 된다.
이 구조가 제대로 되어 있어야:
Deployment
=
서비스 중단
이라는 공식에서 벗어날 수 있다.
현재 프로젝트에서는 처음부터 복잡한 Container Orchestrator를 만드는 것보다:
/health/live
/health/ready
DB Readiness
Optional Dependency 분리
SIGTERM Handler
Readiness OFF
HTTP Drain
Worker Pause
Shutdown Deadline
정도부터 적용하는 것이 가장 현실적이다.
전체 운영 흐름을 연결하면:
Startup
↓
Health Check
↓
Readiness
↓
Traffic
↓
Work Processing
↓
Drain
↓
Graceful Shutdown
↓
Checkpoint / Lease
↓
Restart
↓
Reconciliation
↓
Ready
가 된다.
결국 Health Check와 Graceful Shutdown의 핵심은
“프로세스가 켜져 있느냐”가 아니라, 지금 이 인스턴스가 일을 받아도 되는 상태인지 명확히 판단하고, 내려가야 할 때는 새 일을 먼저 차단한 뒤 이미 맡은 일을 가능한 안전하게 넘겨주고 종료할 수 있게 만드는 것
이다.