ES /docs

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.startTaskfailures=[{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#

  1. 2026-07-09 15:23:08 KSTQMAws::runTask 시작, container instance 4af379b5…listContainerInstances에서 ACTIVE로 반환됨.
  2. 2026-07-09 15:23:08 KSTecs.startTask 호출 결과 failures=[{reason:"DRAINING"}]. logger.error("QMAws::startTask | ECS failures ...") 기록 (본 클러스터의 대표 에러).
  3. 2026-07-09 15:23:19 / 15:23:30 / 15:23:53 KST — SQS 재전달로 동일 메시지가 재처리됨. 이 시점에서는 listContainerInstances가 후보 instance를 리턴하지 않아 task size: 0으로 종료.
  4. 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…로 커밋.
  5. 2026-07-09 15:24:04 KST — job 110318 정상 dispatch 완료.

Error Log#

Datadog Logs

text
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) listContainerInstancesstatus: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으로 전이됩니다. startTaskDRAINING 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:121 listContainerInstances (server-side filter status: 'ACTIVE' and runningTasksCount == 0)
  • 실패 지점: qmAws.ts:196-204 (ecs.startTask 콜백에서 data.failures.length > 0 브랜치)
  • 재시도 트리거: qmJob.ts:171-174tasks.length === 0updateTaskIdToJob returns falserunJobdeleteMessageFromQueue를 skip → SQS 재전달.

listContainerInstances 는 서버 사이드에서 이미 ACTIVE만 필터링하지만, 이 상태 정보는 스냅샷이므로 이후 수 밀리초~수 초 안에 stale해집니다:

packages/cupix-tesla-compute-agent/src/model/qmAws.ts:238-260typescript
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를 호출합니다:

packages/cupix-tesla-compute-agent/src/model/qmAws.ts:137-160typescript
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 레벨로 기록합니다:

packages/cupix-tesla-compute-agent/src/model/qmAws.ts:191-215typescript
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 (재현용):

text
service:cupixworks-any-compute-agent "110318"

Job 110318의 4회 시도 시퀀스 (원본 로그 발췌):

text
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일 동안 반복:

text
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로 관찰된 뒤 같은 초에 startTaskDRAINING을 반환. 같은 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로 이행하거나, ECS PlacementStrategy + 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 사유 실패 카운트 (경보 아님, 트렌드 관찰용):

text
service:cupixworks-any-compute-agent status:error "QMAws::startTask" "DRAINING"

Non-DRAINING (실질적 에러) 카운트 — 이쪽이 진짜 alert 대상:

text
service:cupixworks-any-compute-agent status:error "QMAws::startTask" -"DRAINING"

Job 재시도로 인한 처리 지연 (동일 CPX_JOB_ID 로그가 반복되는지):

text
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 기반 분기 추가)