72 lines
3.5 KiB
Markdown
72 lines
3.5 KiB
Markdown
---
|
|
title: Runbook — async executor rejected
|
|
category: TRANSIENT_DEPENDENCY
|
|
error_codes: [JOB_EXECUTOR_REJECTED]
|
|
severity: P1
|
|
owner: oncall
|
|
last_updated: 2026-06-13
|
|
status: stub
|
|
---
|
|
|
|
# Runbook: async executor rejected (`runbook://job/executor-rejected`)
|
|
|
|
## 1. Trigger
|
|
|
|
이 runbook은 다음 alert에서 발동됩니다.
|
|
|
|
- alert name: `executor_rejected`
|
|
- alert payload 필수 field: `error.code=JOB_EXECUTOR_REJECTED`, `executor_name`, `policy`, `runbook_link`
|
|
- 임계: `executor.rejected.total` > 0 for 1m (p1) — bounded pool이 saturation으로 task를 거부
|
|
- 보조 신호: `executor.saturation` gauge > queue capacity의 80% for 5m (p2)
|
|
|
|
## 2. First Response (5분 이내)
|
|
|
|
### Step 1 — 확인
|
|
1. ERROR log에서 `JOB_EXECUTOR_REJECTED` 라인 확인: `executor_name`, `policy=AbortPolicy`, `queue_size` 추출
|
|
2. `executor.saturation` 패널에서 큐 점유율 추이 확인 — 일시적 burst인지 지속 saturation인지 판별
|
|
3. 동시 유입 원인 파악: 신규 배포 / 트래픽 spike / 다운스트림 지연으로 worker가 장기 점유되는지
|
|
|
|
### Step 2 — 임시 격리
|
|
- AbortPolicy 거부는 호출부에 `RejectedExecutionException`으로 surface됨 — fire-and-forget `@Async` 호출이면 호출부의 async-exception 처리(log/metric)로 흡수됐는지 확인
|
|
- 지속 saturation이면 유입 측(트래픽/스케줄러 빈도)을 우선 감속
|
|
|
|
## 3. Diagnosis
|
|
|
|
- log query: `{service="app"} | error.code="JOB_EXECUTOR_REJECTED" | stats count by executor_name`
|
|
- metric panel: `executor.saturation{executor_name}`, `executor.rejected.total{executor_name, policy}`
|
|
- 가능한 원인 우선순위:
|
|
- 다운스트림 의존성 지연 → worker가 반납되지 않아 큐 포화 (가장 흔함)
|
|
- 트래픽 spike → 정상 부하 한계 초과
|
|
- pool 과소 설정 (`APP_ASYNC_EXECUTOR_*`)
|
|
- non-idempotent 작업이 retry로 누적
|
|
|
|
## 4. Mitigation
|
|
|
|
- 단기: 유입 감속(상위 rate-limit / 스케줄러 interval 확대) 또는 다운스트림 의존성 회복
|
|
- pool 재조정(restart-only): `APP_ASYNC_EXECUTOR_CORE_SIZE` / `APP_ASYNC_EXECUTOR_MAX_SIZE` / `APP_ASYNC_EXECUTOR_QUEUE_CAPACITY`
|
|
— ⚠ queue를 무한정 키우지 말 것(unbounded 금지, D7). 부하테스트로 수치 검증 후 변경
|
|
- CallerRunsPolicy로의 전환은 use-case 차원의 명시적 결정 필요(request thread latency 침식 — TPE-JDK21-C6)
|
|
|
|
## 5. Escalation
|
|
|
|
- 다운스트림 의존성 장애가 근본 원인이면 해당 의존성 오너에게 escalate
|
|
- pool 재조정으로도 saturation이 지속되면 용량 계획(capacity planning) 오너에게 page
|
|
|
|
## 6. Recovery / Verification
|
|
|
|
- 회복 확인: `executor.rejected.total` 증가 멈춤, `executor.saturation` < 80% 정상화
|
|
- 거부된 작업의 재처리 경로(멱등 retry / 다음 스케줄 cycle) 정상 동작 확인
|
|
|
|
## 7. Related
|
|
|
|
- error-codes.yaml rows: `JOB_EXECUTOR_REJECTED` (TRANSIENT_DEPENDENCY, 503, retryable=true, retry_after 5s)
|
|
- metrics.yaml: `executor.rejected.total{executor_name, policy}`, `executor.saturation{executor_name}`
|
|
- 코드: `app-bootstrap` `async/AsyncExecutorConfig`(bounded executor), `async/LoggingAbortPolicy`(reject log+metric), `async/BackgroundJobMetrics`
|
|
- env: `APP_ASYNC_EXECUTOR_CORE_SIZE` / `APP_ASYNC_EXECUTOR_MAX_SIZE` / `APP_ASYNC_EXECUTOR_QUEUE_CAPACITY`
|
|
- 관련 runbook: [[job-timeout]], [[job-dead-letter]]
|
|
- 관련 branch: [[feature-background-job-async-contract]] (D7 saturation policy)
|
|
|
|
---
|
|
|
|
> **Stub 상태 안내**: 이 runbook은 skeleton 단계의 stub. 실제 부하 프로파일·alert 채널·pool 수치 확정 시 보강 필요.
|