ES /docs

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#

  1. 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.
  2. 2026-08-05 16:43:38 KST — SQS visibility timeout 만료 후 재시도, 다시 task length: 0.
  3. 2026-08-05 16:45:10 KST — 재시도, 다른 인스턴스 309c4a42... 에 배치 성공. task arn: arn:aws:ecs:us-west-2:...aae4f63b40db4bc3af77f62ea7a54098.

Error Log#

Datadog Logs

text
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 을 배치할 인스턴스를 고를 때 remainingResourcesMEMORYCPU 만 확인하고 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 는 생성되지 않으므로 빈 배열을 반환하고, updateTaskIdToJobfalse 를 돌려주어 (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:91 runJob
  • 인스턴스 타입 결정: job.manager.ts:101-102 (create_pix_genie_preprocessorAWS_ECS_GPU_INSTANCE_TYPE, 로그상 g6.2xlarge)
  • 배치 시도: job.manager.ts:119 runTask
  • 인스턴스 선별 (failure point): src/model/qmAws.ts:137-159
  • ECS 거부 로깅: src/model/qmAws.ts:196-204
  • 재시도 트리거: qmJob.ts:171-174job.manager.ts:121-126

runTask 는 idle 후보 인스턴스를 순회하면서 MEMORYCPU 잔여량만 조회하고, task 개수만으로 배치 가능 여부를 판단한다. GPU 잔여량은 조회하지 않는다.

applications/agents/packages/cupix-tesla-compute-agent/src/model/qmAws.ts:137-159typescript
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 로 기록된다.

applications/agents/packages/cupix-tesla-compute-agent/src/model/qmAws.ts:191-213typescript
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 루프는 다음 후보로 넘어가거나, 후보가 없으면 빈 배열을 반환한다. 이후 재시도 판정은 아래에서 이루어진다.

applications/agents/packages/cupix-tesla-compute-agent/src/model/qmJob.ts:169-174typescript
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:120donefalsedeleteMessageFromQueue 미호출 → SQS 메시지 잔존 → visibility timeout 후 재시도. receiveMessageVisibilityTimeout 을 지정하지 않아 (qmAws.ts:55-57) 큐 기본값을 사용하며, 로그상 재시도 간격은 약 90 s 였다.

Log Evidence#

Datadog 쿼리:

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

job 1254229 의 실패-재시도-성공 흐름 (KST):

text
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):

text
service:cupixworks-any-compute-agent "ECS failures"
text
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-204data.failures 로깅 심각도를 조정한다. RESOURCE:GPU / DRAINING / AGENT 같은 일시적·자가복구 배치 거부는 warn 으로 낮추고, 재시도 컨텍스트 (job_id, 인스턴스, reason) 를 유지한다. 재시도로 최종 성공하는 조건을 error 로 남기면 알람 노이즈가 된다. 진짜 err 콜백 경로 (qmAws.ts:193) 는 error 유지.

단기 개선 (1주 이내)#

  • 인스턴스 선별 루프 (qmAws.ts:137-159) 에 GPU 잔여량 확인을 추가한다. remainingResources 에서 GPU accelerator 값을 조회해 (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 으로 설정):

text
service:cupixworks-any-compute-agent status:error "ECS failures" "RESOURCE:GPU"

재시도 후 배치 성공 로그 추이 비교용:

text
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 경합이 남을 수 있어 재시도 경로를 건드리지 않는 것이 안전하다.