QMAws::startTask | ECS failures - job_id: 1254229, task_definition: cupix-pix-genie-preprocessor-age
RCA: QMAws::startTask | ECS failures (RESOURCE:GPU)
Overview#
이 인시던트는 cupix-tesla-compute-agent 가 GPU ECS 인스턴스에 작업을 배치하려다 ECS 스케줄러로부터 RESOURCE:GPU 거부를 받고 error 로그를 남긴 건이다. 작업은 실패하지 않았다. SQS 메시지가 삭제되지 않아 재시도되었고, 다음 폴링 사이클에서 다른 GPU 인스턴스에 정상 배치되었다.
What Happened#
2026-08-05 16:42 KST, cupixworks-any-compute-agent (repo cupixworks, 워크스페이스 applications/agents/packages/cupix-tesla-compute-agent) 가 job 1254229 (create_pix_genie_preprocessor, team cana) 를 GPU 인스턴스 e89d162f... 에 배치하려 했으나 ECS 가 RESOURCE:GPU 로 거부했다. 에이전트의 인스턴스 선별 로직은 MEMORY / CPU / task 개수만 검사하고 GPU 잔여량은 검사하지 않아 GPU 가 부족한 인스턴스를 골랐다. startTask 는 이 거부를 error 레벨로 기록했고, SQS 메시지가 삭제되지 않아 job 이 재시도되어 약 3분 뒤 다른 인스턴스에 정상 배치되었다. 사용자 영향은 처리 지연뿐이며 데이터 손실은 없다.
Quick Facts#
| Field | Value |
|---|---|
| exception.class | QMAws::startTask ECS failure (예외 아님, ECS API failures 응답) |
| exception.message | ECS failures ... failures: [{"arn":"...","reason":"RESOURCE:GPU"}] |
| top_frame | src/model/qmAws.ts:197 |
| runtime | Node.js / TypeScript agent (aws-sdk ECS startTask) |
| env | production, us-west-2 (representative), 다른 발생은 eu-central-1 / au 리전 포함 |
Affected Teams#
| Team / Domain | Error Count | Impact |
|---|---|---|
| cana (representative job 1254229) | 1 | GPU preprocessor job 배치 약 3분 지연, 최종 성공 |
전체 (RESOURCE:GPU, now-14d) |
25 | 모두 재시도로 자가 복구, 로그 노이즈 |
Timeline#
- 2026-08-05 16:42:07 KST — job 1254229 이 인스턴스
e89d162f...(running_tasks: 0, pending_tasks: 0, available_memory_mib: 30940) 선택 후startTask호출, ECS 가RESOURCE:GPU거부.runTask | end - task size: 0. - 2026-08-05 16:43:38 KST — SQS visibility timeout 만료 후 재시도, 다시
task length: 0. - 2026-08-05 16:45:10 KST — 재시도, 다른 인스턴스
309c4a42...에 배치 성공.task arn: arn:aws:ecs:us-west-2:...aae4f63b40db4bc3af77f62ea7a54098.
Error Log#
QMAws::startTask | ECS failures - job_id: 1254229, task_definition: cupix-pix-genie-preprocessor-agent-production, cluster: cupix-tesla-ece, container_instance: arn:aws:ecs:us-west-2:002596530511:container-instance/cupix-tesla-ece/e89d162ff08d450793b41c466f033fba, failures: [{"arn":"arn:aws:ecs:us-west-2:002596530511:container-instance/e89d162ff08d450793b41c466f033fba","reason":"RESOURCE:GPU"}]
Impact#
- Service:
cupixworks-any-compute-agent - Team: cana
- 발생 횟수: 1 (cluster) / 25 (
RESOURCE:GPU, now-14d) - 최초 발생: 2026-08-05 16:42 KST
- 최근 발생: 2026-08-05 16:42 KST
Root Cause Summary#
cupix-tesla-compute-agent 는 GPU job 을 배치할 인스턴스를 고를 때 remainingResources 중 MEMORY 와 CPU 만 확인하고 GPU 잔여량은 확인하지 않는다 (qmAws.ts:138-152). 그래서 runningTasksCount == 0 && pendingTasksCount == 0 인 "idle" 인스턴스라도 GPU 가 이미 예약되어 있으면 그 인스턴스를 골라 ecs.startTask 를 호출한다. ECS 스케줄러는 GPU 를 만족시킬 수 없어 failures: [{reason: "RESOURCE:GPU"}] 를 반환한다. startTask 는 이 응답을 error 레벨로 기록하지만 (qmAws.ts:196-204) task 는 생성되지 않으므로 빈 배열을 반환하고, updateTaskIdToJob 이 false 를 돌려주어 (qmJob.ts:171-174) SQS 메시지가 삭제되지 않는다 (job.manager.ts:121-126). 메시지는 visibility timeout 후 다시 표시되어 재시도되고, GPU 가 확보된 인스턴스에 배치되면 성공한다. 즉 root cause 는 배치 실패 자체가 아니라 (1) GPU 잔여량을 무시한 인스턴스 선별 로직과 (2) 일시적/자가복구되는 스케줄러 거부를 error 로 로깅하는 심각도 오분류다.
Technical Analysis#
Code Path#
- Entry point:
src/manager/job.manager.ts:91runJob - 인스턴스 타입 결정:
job.manager.ts:101-102(create_pix_genie_preprocessor→AWS_ECS_GPU_INSTANCE_TYPE, 로그상g6.2xlarge) - 배치 시도:
job.manager.ts:119runTask - 인스턴스 선별 (failure point):
src/model/qmAws.ts:137-159 - ECS 거부 로깅:
src/model/qmAws.ts:196-204 - 재시도 트리거:
qmJob.ts:171-174→job.manager.ts:121-126
runTask 는 idle 후보 인스턴스를 순회하면서 MEMORY 와 CPU 잔여량만 조회하고, task 개수만으로 배치 가능 여부를 판단한다. GPU 잔여량은 조회하지 않는다.
for await (const containerInstance of containerInstances) {
const memoryResource = containerInstance.remainingResources?.find(r => r.name === 'MEMORY');
const cpuResource = containerInstance.remainingResources?.find(r => r.name === 'CPU');
logger.info('QMAws::runTask | job_id: %d, container_instance: %s, status: %s, available_memory_mib: %d, available_cpu: %d, running_tasks: %d, pending_tasks: %d',
job.id,
containerInstance.containerInstanceArn,
containerInstance.status,
memoryResource?.integerValue || 0,
cpuResource?.integerValue || 0,
containerInstance.runningTasksCount,
containerInstance.pendingTasksCount
);
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;
}
}
}
기대 동작은 "GPU 여유가 있는 인스턴스에만 startTask 호출". 실제 동작은 "MEMORY/CPU/idle 조건만 통과하면 GPU 상태와 무관하게 호출". ECS 가 RESOURCE:GPU 로 거부하면 아래에서 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);
}
}
});
data.failures 가 있어도 reject 하지 않고 data.tasks (빈 배열) 를 resolve 한다. 그래서 runTask 루프는 다음 후보로 넘어가거나, 후보가 없으면 빈 배열을 반환한다. 이후 재시도 판정은 아래에서 이루어진다.
const taskLength = tasks.length;
logger.info('QMJob::updateTaskIdToJob | begin - job id: %d, task length: %d', jobId, taskLength);
if (taskLength === 0) {
logger.debug('QMJob::updateTaskIdToJob | end - task empty - job id: %d', jobId);
return resolve(false);
}
resolve(false) → job.manager.ts:120 의 done 이 false → deleteMessageFromQueue 미호출 → SQS 메시지 잔존 → visibility timeout 후 재시도. receiveMessage 는 VisibilityTimeout 을 지정하지 않아 (qmAws.ts:55-57) 큐 기본값을 사용하며, 로그상 재시도 간격은 약 90 s 였다.
Log Evidence#
Datadog 쿼리:
service:cupixworks-any-compute-agent "1254229"
job 1254229 의 실패-재시도-성공 흐름 (KST):
2026-08-05 16:42:07 info QMAws::runTask | begin - job id: 1254229, task def: cupix-pix-genie-preprocessor-agent-production, instance_type: g6.2xlarge, workload: undefined, launch_mode: CUPIXWORKS
2026-08-05 16:42:07 info QMAws::runTask | job_id: 1254229, container_instance: .../e89d162ff08d450793b41c466f033fba, status: ACTIVE, available_memory_mib: 30940, available_cpu: 8192, running_tasks: 0, pending_tasks: 0
2026-08-05 16:42:07 error QMAws::startTask | ECS failures - job_id: 1254229, ... failures: [{"arn":"...e89d162ff08d450793b41c466f033fba","reason":"RESOURCE:GPU"}]
2026-08-05 16:42:07 info QMAws::runTask | end - job id: 1254229, task size: 0
2026-08-05 16:43:38 info QMJob::updateTaskIdToJob | begin - job id: 1254229, task length: 0
2026-08-05 16:45:10 info QMAws::runTask | job_id: 1254229, container_instance: .../309c4a4259184871941b0652a255debb, status: ACTIVE, available_memory_mib: 30940, available_cpu: 8192, running_tasks: 0, pending_tasks: 0
2026-08-05 16:45:10 info QMAws::runTask | end - job id: 1254229, task size: 1
2026-08-05 16:45:10 info QMJob::updateTaskIdToJob | end - job id: 1254229, task arn: arn:aws:ecs:us-west-2:002596530511:task/cupix-tesla-ece/aae4f63b40db4bc3af77f62ea7a54098
거부당한 인스턴스 e89d162f... 는 running_tasks: 0, pending_tasks: 0, available_memory_mib: 30940 로 에이전트 기준 idle 이었지만 ECS 는 GPU 부족으로 거부했다. 이는 GPU 예약 상태가 DescribeContainerInstances 의 task 개수 / remainingResources 뷰에 아직 반영되지 않은 배치 경합, 혹은 이전 task 의 GPU 미해제 상태를 의미한다.
전체 ECS failures 로그의 reason 분포 (now-14d):
service:cupixworks-any-compute-agent "ECS failures"
25 reason: RESOURCE:GPU
22 reason: DRAINING
6 reason: AGENT
task_definition 분포: cupix-pano-postprocessor-production 31, cupix-capture-3d-reconstruction-production 12, cupix-skat-master-production-arm 9, cupix-pix-genie-preprocessor-agent-production 1
cluster 분포: cupix-tesla-ece-eu 24, cupix-tesla-ece 21, cupix-tesla-ece-au 8
RESOURCE:GPU / DRAINING / AGENT 세 reason 모두 같은 startTask 로그 경로를 지나며, 모두 일시적 배치 조건 (GPU 경합 / 스케일인 draining / 에이전트 disconnect) 으로 재시도 시 자가 복구된다. 2026-07-27 20:24-20:27 KST 에는 eu-central-1 의 동일 인스턴스 c28ce0a9... 에 여러 job 이 연달아 RESOURCE:GPU 로 거부되는 버스트가 관측되었다 (하나의 saturated 인스턴스를 반복 선택).
Hypotheses Considered#
| # | Hypothesis | Evidence for | Evidence against | Verdict |
|---|---|---|---|---|
| H1 | 인스턴스 선별이 GPU 잔여량을 무시해 GPU-saturated 인스턴스를 골라 ECS 가 RESOURCE:GPU 거부, 재시도로 자가복구 |
qmAws.ts:138-139 는 MEMORY/CPU 만 조회; 거부 인스턴스 e89d162f 는 idle 로 보였으나 거부됨 (16:42:07 로그); 16:45:10 다른 인스턴스 성공 |
없음 | Confirmed |
| H2 | GPU job 이 영구 실패해 사용자 처리가 중단됨 | task size 0 로그 존재 | job 1254229 가 16:45:10 에 arn aae4f63b... 로 성공; SQS 재시도로 복구 |
Rejected |
| H3 | ECS 계정/리전 GPU 용량 자체 부족 (온디맨드 capacity error) | GPU 부족 메시지 | reason 이 capacity 에러가 아닌 배치 시점 RESOURCE:GPU; 같은 리전 다른 인스턴스에 즉시 배치 성공; auto scaling 경로 (qmAws.ts:123-133) 미진입 |
Rejected |
| H4 | 외부 의존성 인시던트 (ECS 장애) | — | status-board scope svc:cupixworks-any-compute-agent::unknown, active/recent 모두 null |
Rejected |
Fix Recommendation#
즉시 조치 (Critical)#
qmAws.ts:196-204의data.failures로깅 심각도를 조정한다.RESOURCE:GPU/DRAINING/AGENT같은 일시적·자가복구 배치 거부는warn으로 낮추고, 재시도 컨텍스트 (job_id, 인스턴스, reason) 를 유지한다. 재시도로 최종 성공하는 조건을error로 남기면 알람 노이즈가 된다. 진짜err콜백 경로 (qmAws.ts:193) 는error유지.
단기 개선 (1주 이내)#
- 인스턴스 선별 루프 (
qmAws.ts:137-159) 에 GPU 잔여량 확인을 추가한다.remainingResources에서GPUaccelerator 값을 조회해 (MEMORY/CPU와 동일 패턴) GPU 여유가 없는 인스턴스는 건너뛴다. 이렇게 하면 saturated 인스턴스에 대한startTask호출 자체가 줄어RESOURCE:GPU거부 빈도가 감소한다. 단,DescribeContainerInstances뷰의 eventual consistency 때문에 경합은 완전히 제거되지 않으므로 재시도 로직은 유지한다. - 2026-07-27 버스트처럼 동일 인스턴스가 반복 거부되는 경우를 완화하기 위해,
runTask루프가 한 사이클에서 이미RESOURCE:GPU거부한 인스턴스를 같은 사이클 내에서 재선택하지 않도록 스킵 후보에 넣는 것을 검토한다.
장기 개선 (재발 방지)#
- 배치 정책을 에이전트 측 사전 필터링에서 ECS capacity provider / task placement constraints (
attribute:...) 로 이전하는 것을 검토해, GPU 예약 상태를 ECS 스케줄러가 원자적으로 판단하게 한다. 이러면 에이전트-스케줄러 간 뷰 불일치 경합이 근본적으로 줄어든다.
Monitoring#
RESOURCE:GPU 배치 거부 건수를 시각화하는 timeseries widget 쿼리 (group-by 는 widget UI 에서 cluster facet 으로 설정):
service:cupixworks-any-compute-agent status:error "ECS failures" "RESOURCE:GPU"
재시도 후 배치 성공 로그 추이 비교용:
service:cupixworks-any-compute-agent "updateTaskIdToJob | end" "task arn"
RESOURCE:GPU거부가 특정 클러스터/리전에 지속 집중되면 GPU 용량 부족 신호로 간주하고 auto scaling / 인스턴스 풀을 점검한다.- 재시도 후에도 성공 로그가 따라오지 않는 job_id 가 있으면 자가복구 실패로 보고 별도 알람 대상으로 승격한다.
Risk Assessment#
- Risk level: low
- 예상 복잡도: standard
로그 심각도 조정은 trivial 하다. GPU 잔여량 필터 추가는 remainingResources 파싱 패턴이 이미 존재하므로 standard 수준이며, eventual consistency 경합이 남을 수 있어 재시도 경로를 건드리지 않는 것이 안전하다.