QMAws::startTask | ECS failures - job_id: 110318, task_definition: nswgov-skat-master-production-arm
RCA: QMAws::startTask ECS DRAINING failure (job 110318, nswgov skat-master)
Overview#
What Happened#
2026-07-09 15:23 KST (production, ap-southeast-2, tenant nswgov)에 compute agent가 skat-master ECS task를 기동하려던 중, 선택된 container instance가 그 순간 DRAINING 상태로 전이되어 ecs.startTask가 failures=[{reason:"DRAINING"}]를 리턴했습니다. 에러는 1회 기록되었으나 동일 job(110318)은 SQS 재전달 기반 재시도 루프에서 55초 뒤 다른 container instance에서 정상 기동되었습니다.
Quick Facts#
| Field | Value |
|---|---|
| exception.class | QMAws::startTask (ECS API failures[] 반환) |
| exception.message | ECS failures - ... reason:"DRAINING" |
| top_frame | packages/cupix-tesla-compute-agent/src/model/qmAws.ts:197 |
| env | production, ap-southeast-2 (nswgov) |
| cluster | nswgov-cupixworks-ece-au |
| task_definition | nswgov-skat-master-production-arm |
| job_id / capture_id | 110318 / 46879 |
Affected Teams#
| Team / Domain | Error Count | Impact |
|---|---|---|
| sinsw (nswgov tenant) | 1 | 없음 — job 110318이 55초 지연 후 정상 처리됨 (self-recovered) |
Timeline#
- 2026-07-09 15:23:08 KST —
QMAws::runTask시작, container instance4af379b5…가listContainerInstances에서ACTIVE로 반환됨. - 2026-07-09 15:23:08 KST —
ecs.startTask호출 결과failures=[{reason:"DRAINING"}].logger.error("QMAws::startTask | ECS failures ...")기록 (본 클러스터의 대표 에러). - 2026-07-09 15:23:19 / 15:23:30 / 15:23:53 KST — SQS 재전달로 동일 메시지가 재처리됨. 이 시점에서는
listContainerInstances가 후보 instance를 리턴하지 않아task size: 0으로 종료. - 2026-07-09 15:24:03 KST — 새로 스케일된 ACTIVE instance
c1c5f87d…에서startTask성공,updateTaskIdToJob | end - task arn: arn:aws:ecs:ap-southeast-2:...:task/nswgov-cupixworks-ece-au/cb41940e…로 커밋. - 2026-07-09 15:24:04 KST — job 110318 정상 dispatch 완료.
Error Log#
QMAws::startTask | ECS failures - job_id: 110318, task_definition: nswgov-skat-master-production-arm, cluster: nswgov-cupixworks-ece-au, container_instance: arn:aws:ecs:ap-southeast-2:002596530511:container-instance/nswgov-cupixworks-ece-au/4af379b54c9b4b71bdb5676829c19b2e, failures: [{"arn":"arn:aws:ecs:ap-southeast-2:002596530511:container-instance/4af379b54c9b4b71bdb5676829c19b2e","reason":"DRAINING"}]
Impact#
- Service:
cupixworks-any-compute-agent - Team: sinsw
- 발생 횟수: 1
- 최초 발생: 2026-07-09 15:23 KST
- 최근 발생: 2026-07-09 15:23 KST
- 사용자 영향: 없음 (job이 자동 재시도로 55초 안에 성공).
Root Cause Summary#
QMAws.runTask는 (1) listContainerInstances로 status:ACTIVE 이면서 runningTasksCount == 0인 candidate ARN 목록을 받고, (2) 이어서 describeContainerInstances 결과의 status == 'ACTIVE' 를 다시 검사한 뒤, (3) ecs.startTask를 호출합니다. 이 세 API 호출 사이에는 lock이 없고, 그 사이 ASG scale-in 또는 SSM/deployment 이벤트가 해당 EC2 instance에 대해 UpdateContainerAgent/DrainInstance를 트리거하면 instance가 DRAINING으로 전이됩니다. startTask가 DRAINING instance를 인자로 받으면 ECS는 예외가 아니라 failures[] 필드에 {reason:"DRAINING"}을 담아 정상 응답하고, 코드는 이 경우를 무조건 logger.error로 기록합니다 (qmAws.ts:197). 실제로는 상위 runJob이 done=false에서 SQS 메시지를 삭제하지 않아 재시도가 발생하는 정상 recovery 경로이지만, 로그 레벨이 error라 alerting 계층에서 실 장애처럼 보이게 됩니다. 즉, 본 에러는 코드 결함이 아니라 예상되는 race window에서 발생하는 일시적 상태이며, 잘못된 것은 로그 severity입니다.
Technical Analysis#
Code Path#
- Entry:
packages/cupix-tesla-compute-agent/src/manager/job.manager.ts:91(runJob) runTask호출:job.manager.ts:119- Candidate 필터링:
qmAws.ts:117-160(runTask) - Race window 시작:
qmAws.ts:121listContainerInstances(server-side filterstatus: 'ACTIVE' and runningTasksCount == 0) - 실패 지점:
qmAws.ts:196-204(ecs.startTask콜백에서data.failures.length > 0브랜치) - 재시도 트리거:
qmJob.ts:171-174—tasks.length === 0→updateTaskIdToJobreturnsfalse→runJob이deleteMessageFromQueue를 skip → SQS 재전달.
listContainerInstances 는 서버 사이드에서 이미 ACTIVE만 필터링하지만, 이 상태 정보는 스냅샷이므로 이후 수 밀리초~수 초 안에 stale해집니다:
private listContainerInstances = (awsClusterName: string, instanceType: string): Promise<Array<string>> => new Promise((resolve, reject) => {
logger.debug('QMAws::listContainerInstances | begin - cluster name: %s, instance type: %s', awsClusterName, instanceType);
const params: AWS.ECS.ListContainerInstancesRequest = {
cluster: awsClusterName,
status: 'ACTIVE',
filter: `attribute:ecs.instance-type == ${instanceType} and runningTasksCount == 0`,
maxResults: 10
};
this.ecs.listContainerInstances(params, (err, data) => {
// ...
});
});
이후 runTask 는 후보 각각에 대해 in-process 재검사 후 startTask를 호출합니다:
for await (const containerInstance of containerInstances) {
// ... memory/cpu logging ...
if (containerInstance.status == 'ACTIVE'
&& containerInstance.runningTasksCount == 0
&& containerInstance.pendingTasksCount == 0
) {
try {
tasks = await this.startTask(awsClusterName, containerInstance.containerInstanceArn as string, job);
if (tasks.length > 0) break;
} catch (error) {
return;
}
}
}
startTask 자체는 ECS 실패를 항상 error 레벨로 기록합니다:
this.ecs.startTask(params, (err, data) => {
if (err) {
logger.error('QMAws::startTask | end - %s', err.message);
reject(err);
} else {
if (data.failures && data.failures.length > 0) {
logger.error('QMAws::startTask | ECS failures - job_id: %d, task_definition: %s, cluster: %s, container_instance: %s, failures: %s',
job.id,
taskDefinition,
awsClusterName,
containerInstanceArn,
JSON.stringify(data.failures.map(f => ({ arn: f.arn, reason: f.reason, detail: f.detail })))
);
}
const tasks = data.tasks;
if (tasks == undefined) {
logger.info('QMAws::startTask | end - task empty');
resolve([]);
} else {
logger.debug('QMAws::startTask | end - task size: %d', tasks.length);
resolve(tasks);
}
}
});
기대 동작: DRAINING은 정상적 lifecycle 상태이고 상위에서 재시도가 이미 설계되어 있으므로 warn 레벨이 적절함. 실제 동작: error 레벨로 기록되어 alerting/error-sweeper cluster로 승격됨.
Log Evidence#
Datadog query (재현용):
service:cupixworks-any-compute-agent "110318"
Job 110318의 4회 시도 시퀀스 (원본 로그 발췌):
15:23:08 info QMAws::runTask | begin - job id: 110318, task def: nswgov-skat-master-production-arm, instance_type: m7g.8xlarge, workload: undefined, launch_mode: CUPIXWORKS
15:23:08 info QMAws::runTask | job_id: 110318, container_instance: 4af379b54c9b4b71bdb5676829c19b2e, status: ACTIVE, available_memory_mib: 126943, available_cpu: 32768, running_tasks: 0, pending_tasks: 0
15:23:08 error QMAws::startTask | ECS failures - job_id: 110318, ... failures: [{"arn":"...4af379b5...","reason":"DRAINING"}]
15:23:08 info QMAws::runTask | end - job id: 110318, task size: 0
15:23:08 info QMJob::updateTaskIdToJob | begin - job id: 110318, task length: 0 ← done=false → SQS 재전달
15:23:19 info QMAws::runTask | begin - job id: 110318, ... ← 재시도 #2
15:23:19 info QMAws::runTask | end - job id: 110318, task size: 0
15:23:30 info QMAws::runTask | begin - job id: 110318, ... ← 재시도 #3
15:23:30 info QMAws::runTask | end - job id: 110318, task size: 0
15:23:53 info QMAws::runTask | begin - job id: 110318, ... ← 재시도 #4
15:23:53 info QMAws::runTask | end - job id: 110318, task size: 0
15:24:03 info QMAws::runTask | job_id: 110318, container_instance: c1c5f87de27e4505ba1372e065026516, status: ACTIVE, available_memory_mib: 126943, ..., running_tasks: 0, pending_tasks: 0
15:24:03 info QMAws::runTask | end - job id: 110318, task size: 1 ← 성공
15:24:04 info QMJob::updateTaskIdToJob | end - job id: 110318, task arn: arn:aws:ecs:ap-southeast-2:002596530511:task/nswgov-cupixworks-ece-au/cb41940e4716412da3642e079ccb0887
동일 패턴(다른 tenant/region)이 지난 3일 동안 반복:
service:cupixworks-any-compute-agent status:error @environment:production "QMAws::startTask" (last 3d, 18 hits)
- DRAINING reasons: 09/15:23 nswgov-au, 09/10:23 cupix us-west-2, 08/10:13 (3건 동시) cupix us-west-2, 07/09:33 (2건), 07/05:23, 07/04:53, 06/21:49 (3건), 06/21:33
- RESOURCE:GPU reasons: 09/07:07 (2건), 09/07:05, 09/06:56
18건 중 다수 DRAINING, 소수 RESOURCE:GPU. DRAINING은 tenant/region 무관하게 모두 재시도로 self-recover 하는 패턴.
Hypotheses Considered#
| # | Hypothesis | Evidence for | Evidence against | Verdict |
|---|---|---|---|---|
| H1 | ASG scale-in / SSM 이벤트로 listContainerInstances~startTask 사이에서 선택된 EC2가 DRAINING으로 전이됨 (race condition, 정상 recovery 있음) |
4af379b5…는 runTask 로그에서 status: ACTIVE로 관찰된 뒤 같은 초에 startTask가 DRAINING을 반환. 같은 job이 다른 instance(c1c5f87d…)에서 55초 뒤 성공. 3일간 여러 tenant/region에서 동일 패턴 재현. |
— | Confirmed |
| H2 | ECS API/AWS control plane 장애 | 다른 시각 다른 tenant/region에서 개별 발생하며 다른 로그(runTask begin/end)는 정상. describeContainerInstances, listContainerInstances도 정상 동작. status-board에 관련 dep 인시던트 없음. |
— | Rejected |
| H3 | Task definition (nswgov-skat-master-production-arm) 자체가 잘못됨 |
— | 같은 task definition으로 55초 뒤 성공. 다른 task def(cupix-pano-postprocessor-production, cupix-skat-master-production-arm, cupix-capture-3d-reconstruction-production)에서도 동일 패턴. |
Rejected |
| H4 | job 110318 처리 실패로 사용자 캡처(46879)가 유실 | — | `updateTaskIdToJob | end - job id: 110318, task arn: ...cb41940e…로그로 정상 dispatch 확인.deleteMessageFromQueue`는 done=true일 때만 호출되므로 실패 시 SQS 재전달, 성공 시 삭제. |
| H5 | RESOURCE:GPU 실패(H1과 별도 하위 클러스터)도 동일 원인 | 같은 코드 경로에서 data.failures[].reason이 다른 값(GPU 자원 부족)일 때도 동일 error 발생. |
근본 원인은 GPU capacity로 다름 (스케일 이슈). 본 클러스터의 대표 에러는 DRAINING이므로 별도 인시던트. |
Inconclusive — 본 RCA 범위 밖 |
Fix Recommendation#
즉시 조치 (Critical)#
없음. 사용자 영향이 없고 (job은 재시도로 성공), 데이터 손상도 없음.
단기 개선 (1주 이내)#
packages/cupix-tesla-compute-agent/src/model/qmAws.ts:196-204의 로그 severity 조정.data.failures[].reason === 'DRAINING'인 경우는logger.warn으로 downgrade.DRAINING은 ASG lifecycle의 정상 상태이며 상위 재시도 루프가 존재하므로 error alert 대상이 아님.- 그 외 reason (
RESOURCE:*,AGENT, unknown)은 기존대로error유지.RESOURCE:GPU처럼 capacity 신호는 별도 판단이 필요하므로 error를 유지하는 편이 안전함. - 근거: 팀 로거 컨벤션 (
cplogger) 기준, expected-but-undesired 이벤트는warn이 관례. 본 RCA는 코드 스니펫만 제시하고 실제 구현은 code-fix 단계에서 진행.
장기 개선 (재발 방지)#
- Race window를 줄이려면
startTask대신runTask(capacity provider 기반 placement) API로 이행하거나, ECSPlacementStrategy+ service scheduler 사용을 검토. 다만 현재 아키텍처(1 task/instance, custom placement)와 호환성 검토 선행 필요. - Container instance drain 예정 정보 (ASG lifecycle hook, ECS
updateContainerInstancesState) 를 pre-fetch하여 후보에서 제외하는 캐시 계층 추가는 복잡도가 커서 우선순위 낮음. RESOURCE:GPU계열 실패는 별도 클러스터로 트래킹되면 GPU ASG desired capacity 조정 관점에서 RCA 진행 권장.
Monitoring#
DRAINING race의 발생 빈도와 recovery 지연을 관찰하기 위한 지표.
DRAINING 사유 실패 카운트 (경보 아님, 트렌드 관찰용):
service:cupixworks-any-compute-agent status:error "QMAws::startTask" "DRAINING"
Non-DRAINING (실질적 에러) 카운트 — 이쪽이 진짜 alert 대상:
service:cupixworks-any-compute-agent status:error "QMAws::startTask" -"DRAINING"
Job 재시도로 인한 처리 지연 (동일 CPX_JOB_ID 로그가 반복되는지):
service:cupixworks-any-compute-agent "QMAws::runTask | begin"
권장 alerting 정책: 5분 창에서 non-DRAINING QMAws::startTask error가 3건 이상일 때만 페이지. DRAINING만 발생하는 경우는 dashboard 트렌드 위젯으로만 노출.
Risk Assessment#
- Risk level: low
- 예상 복잡도: trivial (로그 레벨 조정 1개 파일,
data.failures[].reason기반 분기 추가)