ES /docs

BaseTaskContainer::checkTaskFail | Failed - Total: 500, Done: 285, Failed: 10

RCA: BaseTaskContainer::checkTaskFail | Failed - Total: 500, Done: 285, Failed: 10

Error Log#

Datadog Logs

text
BaseTaskContainer::checkTaskFail | Failed - Total: 500, Done: 285, Failed: 10

Impact#

  • Service: cupixworks-capture-postprocessor-agent
  • Team: tridentconstruction
  • 발생 횟수: 206
  • 최초 발생: 2026-04-13T21:29:55.767Z
  • 최근 발생: 2026-04-13T21:30:16.591Z

Root Cause Summary#

tridentconstruction 팀의 postprocessor agent (capture 680042, job 1012296)가 pano 업로드 시 잘못된 capture_id(680039, metconus 팀 소유)를 사용하여 API를 호출했습니다. Tesla API의 권한 검사 (CaptureRepository#permission_joins)에서 tridentconstruction 사용자(Liam Hudock, user_id 32496)가 metconus 소유 capture 680039에 접근 권한이 없어 HTTP 403 (Cupix::Errors::NotFound, code ARG10002, "Capture not found")을 반환했습니다. HTTP 403은 agent SDK에서 HttpError로 throw되어 init() 단계에서 즉시 실패하고, pano가 생성되지 않아 upload_url이 설정되지 않았습니다. checkStatusCode()가 403을 비재시도 대상(400 < 403 < 500)으로 판단하여 failTask()를 즉시 호출, count: 0/5로 재시도 없이 실패했습니다.

잘못된 capture_id 사용 원인: agent는 workspace 디렉토리 /tmp/workspace/680039/videos/640181/의 파일을 처리했습니다. EFS의 preprocessor 메타데이터(cpcapture.json)에 srvVideo.capture.id = 680039가 임베딩되어 있었고, CPVideo.fromMeta() (cpvideo.ts:108-124)가 이를 검증 없이 역직렬화하여 잘못된 capture_id가 createPanoRequest에 전파되었습니다.

workspace가 cleanup되지 않은 원인: 이전 job 1012095 (capture 680039)은 20:03:37에 최초 실행되어 20:09:51에 workspace가 정상 cleanup되었습니다. 그러나 SQS visibility timeout 만료로 21:15:48에 동일 메시지가 재수신되어 copySourceDir() (file-system.manager.ts:413-445)가 EFS에서 /tmp/workspace/680039/로 파일을 다시 복사했습니다. 재처리 중 21:26:59에 RESC10000 ("Resource does not uploaded") 에러로 실패했고, handlingMessageErrors() (base-service.ts:290-313)는 SQS 메시지 삭제만 수행하고 workspace cleanup을 호출하지 않았습니다. cleanUpAnythingRelatedModel() (base-service.ts:204-222)은 runByMessage()의 성공 경로(line 171)에서만 호출되며, 에러 경로에서는 호출되지 않습니다. 결과적으로 /tmp/workspace/680039/가 디스크에 잔존한 상태에서, 직후 21:27:00에 job 1012296 (capture 680042)이 동일 agent에서 실행되어 stale workspace의 영향을 받았습니다.

285개의 성공한 pano는 올바른 capture 680042에 대해 생성된 것이며, 실패한 215개는 잘못된 capture 680039에 대해 시도된 것입니다.

Technical Analysis#

Code Path#

1. API 서버 측 — upload_url 생성 경로#

upload_url은 Tesla API에서 다음 체인을 통해 생성됩니다:

  • PanoSerializer (pano_serializer.rb:110-116)에서 resource_state_name[:created, :uploading, :missing]에 포함될 때만 upload_url 반환
ruby
# pano_serializer.rb:110-116
attribute :upload_url do |pano, params|
  if %i[created uploading missing].include?(pano.resource_state_name)
    pano.resource_upload_url
  else
    nil
  end
end
  • Resourcable::Pano#resource_upload_url (resourcable/pano.rb:47-49) → Resourcable#upload_url (resourcable.rb:70-80) → Storagable::Resource#presigned_upload_url (storagable/resource.rb:111-131)
ruby
# storagable/resource.rb:111-131
def presigned_upload_url(revision, force: false)
  revision ||= self.revision + 1
  client = Cupix::StorageService.client(storage_option: storage_option)
  signer = Aws::S3::Presigner.new(client: client)
  signer.presigned_url(:put_object, bucket: bucket_name, key: object(revision).key, ...)
end
  • Aws::S3::Presigner로컬 암호화 서명 연산이며 네트워크 호출 없음 — S3 rate limiting이나 IAM throttling은 원인이 아님

  • upload_urlnil이 되는 조건:

    1. resource_state_name[:created, :uploading, :missing]에 없을 때 (e.g., :uploaded)
    2. resource association이 nil일 때 (resourcable.rb:71-73)
    3. Pano 자체가 생성되지 않았을 때 (HTTP 403/에러 응답) — 이번 사건의 원인

2. API 서버 측 — 권한 검사 (403 발생 경로)#

  • PanosController#create (panos_controller.rb:43-47) → PanoFactory#create! (pano_factory.rb:7-16)에서 CaptureRepository.new(current_user: self.current_user).show(params[:capture_id]) 호출
  • BaseRepository#show (base_repository.rb:327-353)에서 permission_joins()로 팀 권한 검사 수행
  • tridentconstruction 사용자가 metconus 소유 capture 680039에 권한 없음 → model = nilCupix::Errors::NotFound (code ARG10002) raise
  • ClientErrorController (client_error_controller.rb:27,55-57)에서 NotFoundHTTP 403으로 변환
ruby
# client_error_controller.rb:27
rescue_from Cupix::Errors::NotFound, with: :not_found_403_error

# client_error_controller.rb:55-57
def not_found_403_error(exception)
  raise_error(403, exception)
end

3. Agent 측 — HTTP 403 처리 경로#

  • cupixApi.pano.create() → Tesla SDK (panoApi.js:1026-1040)에서 status 200-299 범위 외의 응답은 HttpError로 reject
  • cupixRetriableRequest()에서 403은 retriable 코드(408, 429, 500, 502, 503, 504)에 포함되지 않아 에러 전파
  • UploadNewPanosTask.init() (upload-new-panos.task.ts:39)에서 HttpError throw → responsePano 할당 안 됨 → setServerPanoData() 미호출 → _uploadUrl 미설정
  • transferTask() catch → retryTask()checkStatusCode(error) (transfer.manager.ts:168-171)
typescript
// transfer.manager.ts:168-171
private checkStatusCode = (error: any): boolean => {
    if (error?.statusCode != undefined && error.statusCode > 400 && error.statusCode < 500) return false;
    return true;
};
  • 403은 400 < 403 < 500이므로 false 반환 → 재시도 불가 → 즉시 failTask()count: 0/5로 로그

4. 잘못된 capture_id 전파 경로 (근본 원인)#

  • Agent 세션 생성: capture.user.agent_team_session(capture.team) (processible_capture.rb:59-65, user.rb:116-126) — capture 680042의 owner(tridentconstruction) 세션으로 정상 생성
  • SQS 메시지에 session.token 포함 (cws/capture_postprocessor_agent.rb:19-39)
  • Agent가 job 로드 시 FileSystemManager.loadCPCaptureModel() (file-system.manager.ts:103-136)에서 EFS의 preprocessor 메타데이터(cpcapture.json) 읽기
  • CPCapture.fromMeta() (cpcapture.ts:622-639)에서 CPVideo.fromMeta(videoMeta) 호출
  • CPVideo.fromMeta() (cpvideo.ts:108-124)에서 meta.srvVideo에 임베딩된 이전 capture (680039) 데이터를 검증 없이 this._serverModel에 설정
  • CPCapture.fromMeta() (cpcapture.ts:601)에서 capture ID 불일치 검사 존재하나, 비디오 메타데이터의 임베딩된 capture ID는 재귀적으로 검증하지 않음
typescript
// cpcapture.ts:601
if (metaCaptureId == undefined || (captureId != undefined && captureId !== metaCaptureId)) {
    logger.warn('CPCapture::fromMeta | capture ID is different...');
    return false;
}
  • UploadNewPanosTask.init()createPanoRequest에서 capture_id: cpCapture.id를 사용하지만, 500개 task 중 215개가 잘못된 메타데이터 경로(/tmp/workspace/680039/)의 파일을 참조

5. Workspace Cleanup 실패 경로 (stale workspace 잔존 원인)#

Agent의 workspace cleanup은 BaseService.cleanUpAnythingRelatedModel() (base-service.ts:204-222)에서 수행됩니다:

typescript
// base-service.ts:204-222
private cleanUpAnythingRelatedModel = (): Promise<void> => new Promise(resolve => {
    if (Environment.DEBUG_MODE && Environment.CPX_SKIP_CLEANUP_DATA) {
        return resolve(); // skip in debug mode
    }
    if (this._modelDirPath) {
        CPUtils.deleteFolderRecursive(this._modelDirPath);
        this._modelDirPath = undefined;
        resolve();
    } else {
        resolve(); // undefined modelDirPath — no cleanup
    }
});

이 함수는 runByMessage() (base-service.ts:153-191)의 성공 경로에서만 호출됩니다:

typescript
// base-service.ts:164-185
try {
    await TraceUtils.activateSpan(span, async () => {
        await this.authenticateByMessage(msgObject);
        await this.run(targetId, msgObject);          // ← 여기서 에러 발생 시
        await this.cleanUpAnythingRelatedModel();      // ← 이 줄에 도달하지 못함
        await this.deleteByMessage(message);
    });
} catch (error) {
    throw error; // ← 에러가 상위로 전파
}

에러 발생 시 flow:

  1. this.run() (line 170) → PostprocessorService::run()FinalizationServiceUploadNewPanosTaskcheck_uploading API 호출 실패 → throw
  2. catch (error) (line 182) → throw error → 상위 checkingQueue() (line 109)의 catch
  3. handlingMessageErrors() (base-service.ts:290-313) 호출:
typescript
// base-service.ts:290-313
private handlingMessageErrors = async (error: any): Promise<void> => {
    if (this.messageInProcess) {
        const apiErrorObject = this.getApiErrorToDeleteMessage(error);
        if (apiErrorObject != undefined || this.checkReceiveCountToDeleteMessage()) {
            await this.deleteByMessage(this.messageInProcess);          // SQS 메시지만 삭제
            if (this._modelInProcess) await this.updateErrorState(...); // 에러 상태 업데이트
        }
        // ❌ cleanUpAnythingRelatedModel() 호출 없음 — workspace 잔존
    }
};

SQS 재수신에 의한 workspace 재생성: workspace /tmp/workspace/680039/는 20:09:51에 정상 cleanup되었지만, SQS visibility timeout 만료로 21:15:48에 job 1012095이 재수신됨. PostprocessorService::run()fileSystemManager.copySourceDir(cpCapture) (postprocessor-service.ts:103)에서 EFS의 preprocessor 결과를 workspace에 다시 복사:

typescript
// file-system.manager.ts:413-427
copySourceDir = (cpCapture: CPCapture): Promise<boolean> => new Promise((resolve, reject) => {
    const preProcessorResultDir = path.join(this.preProcessorDirPath, Constants.DefaultResultsDirName);
    CPUtils.copyFolderRecursiveSync(preProcessorResultDir, cpCapture.workspaceDirPath, false);
    // video frame files 복사 → /tmp/workspace/680039/videos/640181/original/ 재생성
});

재처리가 21:26:59에 실패한 후, 에러 경로에서 cleanup이 호출되지 않아 /tmp/workspace/680039/가 잔존. Job 1012296 (capture 680042)이 21:27:00에 동일 agent에서 실행되면서 stale workspace의 파일을 참조하게 됨.

기대 동작: capture 680042의 pano만 생성되어 모두 upload_url 포함 응답을 받아 정상 업로드.

실제 동작: 285개는 올바른 capture 680042로 성공, 215개는 잘못된 capture 680039으로 API 호출 → HTTP 403 → init() 실패 → url: undefined → 즉시 failTask() → 206건의 checkTaskFail 에러 로그 생성.

Log Evidence#

사용한 Datadog 쿼리:

text
service:cupixworks-capture-postprocessor-agent status:error @environment:production "BaseTaskContainer::checkTaskFail"
text
service:cupixworks-capture-postprocessor-agent status:error @environment:production "TransferManager::failTask"
text
service:cupixworks-api status:error @environment:production @http.status_code:403 "680039"
text
service:cupixworks-api status:info @environment:production "680042" "panos"

실패 타임라인:

Phase 1 — 정상 업로드 (21:25:53 ~ 21:26:56, capture 680042):

text
21:25:53.047Z  UploadNewPanosTask::init | begin - cpPano target: /tmp/workspace/680039/videos/640181/original/640181_00696.jpg
21:25:53.061Z  createPanoRequest - {"capture_id":680039,"video_id":640181,"name":"VID_...(696).jpg",...}
21:25:53.XXX  TransferManager::uploadFile | begin path: .../640181_00696.jpg, url: https://s3.amazonaws.com/cupixworks-source-dc9dcff32488-usea1/resources/.../usea1/v1?...

285개의 pano가 이 패턴으로 정상 업로드.

Phase 2 — 첫 번째 실패 (유효한 URL, 재시도 소진):

text
21:26:56.307Z  TransferManager::failTask | path: /tmp/workspace/680039/videos/640181/original/640181_00674.jpg, url: https://s3.amazonaws.com/cupixworks-source-dc9dcff32488-usea1/resources/jaxpgh/usea1/v1?..., count: 5/5
21:26:56.306Z  BaseTaskContainer::checkTaskFail | Failed - Total: 500, Done: 271, Failed: 1

Phase 3 — 연쇄 실패 (url: undefined, HTTP 403, 21:29:54 ~ 21:30:16):

text
21:29:54.969Z  BaseTaskContainer::checkTaskFail | Failed - Total: 500, Done: 285, Failed: 2
21:29:54.970Z  TransferManager::failTask | path: /tmp/workspace/680039/videos/640181/original/640181_01006.jpg, url: undefined, count: 0/5
21:29:55.XXX  TransferManager::failTask | path: .../640181_01007.jpg, url: undefined, count: 0/5
...
21:30:16.591Z  BaseTaskContainer::checkTaskFail | Failed - Total: 500, Done: 285, Failed: 215

API 측 403 에러 (cupixworks-api, 같은 시간대):

text
21:29:55Z ~ 21:30:18Z  POST /api/v1/panos (capture_id=680039) → HTTP 403
    user: Liam Hudock (id: 32496, tridentconstruction)
    exception: Cupix::Errors::NotFound (code: ARG10002, "Capture not found")
    214 occurrences

API 측 정상 응답 (cupixworks-api, capture 680042):

text
21:30:17Z  POST /api/v1/panos (capture_id=680042) → HTTP 200
22:30:47Z ~ 22:31:23Z  POST /api/v1/panos (capture_id=680042) → HTTP 200 (241건)
    user: Liam Hudock (id: 32496, tridentconstruction)
    정상 응답 — upload_url 포함

Workspace Cleanup 로그 (stale workspace 잔존 증거):

text
service:cupixworks-capture-postprocessor-agent @environment:production "cleanUpAnythingRelatedModel" "680039"
text
service:cupixworks-capture-postprocessor-agent @environment:production "1012095"
시각 (UTC) 이벤트
20:03:37.734 BaseService::runByMessage | id: 1012095 — job 1012095 최초 수신
20:09:51.546 cleanUpAnythingRelatedModel | path: /tmp/workspace/680039 — 정상 cleanup
21:15:48.734 BaseService::runByMessage | id: 1012095 — SQS 재수신 (visibility timeout 만료)
21:26:56.309 getApiErrorToDeleteMessage — RESC10000 "Resource does not uploaded", modelId: 1012095
21:26:59.647 handlingMessageErrors — 에러 처리 (SQS 메시지만 삭제, workspace cleanup 없음)
21:27:00.204 BaseService::runByMessage | id: 1012296 — capture 680042 job 실행 (stale workspace 680039 존재)

Job 1012095의 재처리(21:15:48)에서 copySourceDir()가 EFS에서 /tmp/workspace/680039/로 파일을 복사했고, 21:26:59에 RESC10000 에러로 실패. handlingMessageErrors()가 호출되었지만 workspace cleanup은 수행하지 않음 — 에러 경로에 cleanUpAnythingRelatedModel() 호출이 없기 때문. 잔존한 workspace가 직후 job 1012296에 영향.

핵심 관찰:

  • 연쇄 실패 시 모든 task가 count: 0/5로 재시도 없이 즉시 실패 — HTTP 403의 statusCodecheckStatusCode()에서 비재시도로 분류
  • 정상 phase의 createPanoRequest에서 capture_id: 680039를 사용 — workspace 경로에서 파생된 잘못된 ID
  • API 서버 로그에서 cupixworks-api의 error-level 로그 0건 — 403은 정상적인 권한 거부 응답
  • cupixworks-api warn 로그 53건의 Pano#_update_document: NotFound — Elasticsearch 인덱싱 지연 (직접 원인 아님)

Fix Recommendation#

즉시 조치 (Critical)#

  1. 에러 경로에서 workspace cleanup 추가base-service.ts:290-313

    • handlingMessageErrors()cleanUpAnythingRelatedModel() 호출 추가. 현재 에러 경로에서는 SQS 메시지 삭제만 수행하고 workspace 디렉토리를 삭제하지 않아, 실패한 job의 workspace가 동일 agent의 후속 job에 영향
    • this._modelDirPath가 설정된 경우 (setModelWorkspace() 호출 후 에러 발생 시) cleanup이 수행되어야 함
  2. 메타데이터 capture_id 검증 추가cpcapture.ts:622-639

    • CPCapture.fromMeta()에서 비디오 메타데이터의 임베딩된 srvVideo.capture.id가 현재 job의 capture_id와 일치하는지 검증
    • 불일치 시 경고 로그 + 현재 job의 capture_id로 override하거나 해당 비디오 skip
  3. job 시작 시 stale workspace 감지postprocessor-service.ts:80-83

    • createCPCaptureByJob()setModelWorkspace() 호출 전에, /tmp/workspace/ 하위에 현재 capture_id가 아닌 다른 디렉토리가 존재하면 경고 로그 출력 + 삭제
    • 이는 방어적 조치로, cleanup 누락이나 컨테이너 재사용에 의한 stale workspace 영향을 차단
  4. HTTP 403 에러의 명확한 로깅upload-new-panos.task.ts

    • init()에서 API 호출 실패 시 error.statusCodeerror.body를 명시적으로 로그하여 url: undefined만으로는 알 수 없는 실제 원인 파악 가능하도록 개선

단기 개선 (1주 이내)#

  1. checkTaskFail의 중복 콜백 호출 방지base.container.ts:47-53

    • 현재 _callbackFailed가 실패마다 반복 호출되어 Promise가 이미 reject된 후에도 계속 호출됨
    • 최초 1회만 콜백을 호출하도록 guard 추가 (e.g., _failedCallbackCalled 플래그)
  2. failTask의 전체 큐 중단 로직 검토transfer.manager.ts:158-160

    • 단일 task 실패 시 _stop = true로 전체 큐를 중단하는 것이 의도된 동작인지 검토
    • 대량 배치에서 일부 실패가 전체 작업을 중단시키는 문제

장기 개선 (재발 방지)#

  1. SQS 멱등성 보장: job 1012095이 SQS visibility timeout 만료로 재수신되어 workspace를 재생성한 것이 근본 트리거. SQS 메시지 수신 시 processing_status 체크(postprocessor-service.ts:68-70)가 이미 존재하지만, 재처리 시 start_{action}_agent 상태가 아닌 경우 skip하지 못함. job 상태 기반 멱등성 검증 강화 필요
  2. EFS 메타데이터 무결성 검증: preprocessor → postprocessor 간 전달되는 cpcapture.json에 capture_id 검증 체크섬 추가
  3. 임베딩된 capture 참조 제거: CPVideoMeta에서 전체 srvVideo.capture 객체 대신 capture_id만 참조하도록 경량화
  4. Agent workspace 완전 격리: workspace 경로를 /tmp/workspace/{capture_id}/ 대신 /tmp/workspace/{job_id}/로 변경하여 서로 다른 job 간 파일 간섭을 원천 차단

Monitoring#

  • 추가할 메트릭: url: undefined 실패 횟수를 별도로 추적하는 커스텀 로그 또는 메트릭
  • Datadog 쿼리 예시:
text
service:cupixworks-capture-postprocessor-agent status:error "TransferManager::failTask" "url: undefined"
text
service:cupixworks-api status:error @http.status_code:403 "Capture not found" @usr.team_domain:*
  • checkTaskFail의 Failed 비율이 Total의 30%를 초과하면 알림 트리거 권장

Risk Assessment#

  • Risk level: medium
  • 예상 복잡도: standard — cpcapture.tsfromMeta() 메타데이터 검증과 file-system.manager.ts의 workspace 격리가 핵심. 다른 agent 패키지(preprocessor, 3d-reconstruction, room)에도 동일한 메타데이터 로딩 패턴이 존재하므로 일관된 수정 필요

Revision History#

Revision 1#

Feedback: upload_url 이 누락된 이유를 더 자세하게 조사좀

판정:

피드백 항목 판정 근거
upload_url 누락 원인 상세 조사 수용 기존 분석은 "API가 upload_url 없이 응답을 반환"이라고만 기술하고 서버 측 원인을 특정하지 못했음. 재조사 결과: (1) PanoSerializer (pano_serializer.rb:110-116)에서 upload_urlresource_state_name ∈ [:created, :uploading, :missing]일 때만 반환되며, (2) Aws::S3::Presigner (storagable/resource.rb:111-131)는 로컬 암호화 서명으로 S3 네트워크 호출 없음 — S3 rate limiting/IAM throttling 가설 기각. (3) Datadog API 로그에서 capture 680039에 대한 214건의 HTTP 403 확인 (Cupix::Errors::NotFound, code ARG10002), capture 680042에 대한 246건 전부 HTTP 200 확인. (4) 403 원인은 CaptureRepository#permission_joins (base_repository.rb:327-353)에서 tridentconstruction 사용자의 metconus capture 접근 차단. (5) 근본 원인은 CPVideo.fromMeta() (cpvideo.ts:108-124)가 이전 job의 메타데이터에 임베딩된 stale capture_id를 검증 없이 사용한 것.

변경 사항:

  • Root Cause Summary: "API가 upload_url 없이 응답" → "잘못된 capture_id(680039)로 API 호출 → HTTP 403 → upload_url 미생성"으로 근본 원인 교정
  • Technical Analysis > Code Path: API 서버 측 upload_url 생성 경로 (PanoSerializer → Resourcable → Storagable → Aws::S3::Presigner), 권한 검사 경로 (CaptureRepository#permission_joins → HTTP 403), agent HTTP 403 처리 경로, 잘못된 capture_id 전파 경로 (CPVideo.fromMeta의 stale metadata) 4개 섹션 추가
  • Log Evidence: cupixworks-api 측 403 에러 214건 및 정상 200 응답 로그 추가
  • Fix Recommendation: "upload_url 누락 시 재시도"에서 "메타데이터 capture_id 검증 + workspace 격리 + HTTP 403 로깅"으로 핵심 수정 대상 교정. S3 rate limiting 관련 장기 개선 항목 제거

추가 조사 내용:

  • tesla 레포 (/home/ec2-user/repos/tesla): PanoSerializer, CaptureRepository, Resourcable, Storagable, ClientErrorController 코드 분석
  • cupixworks 레포 (/home/ec2-user/repos/cupixworks): Tesla SDK panoApi.js의 HTTP 에러 핸들링, cupixRetriableRequest의 재시도 코드 목록, CPVideo.fromMeta() 메타데이터 역직렬화, FileSystemManager.loadCPCaptureModel() 분석
  • Datadog 로그: cupixworks-api의 capture 680039 (403 × 214건) 및 capture 680042 (200 × 246건) 검증, cupixworks-capture-postprocessor-agent의 failTask 로그 패턴 분석

Revision 2#

Feedback: workspace 가 cleanup 되지 않는 원인을 다시 찾아

판정:

피드백 항목 판정 근거
workspace cleanup 실패 원인 재조사 수용 기존 분석은 "이전 metconus 작업이 남긴 것"이라고만 기술하고 cleanup이 왜 안 되었는지 코드 경로를 특정하지 못했음. 재조사 결과: (1) cleanUpAnythingRelatedModel() (base-service.ts:204-222)은 runByMessage()의 성공 경로(line 171)에서만 호출되며, 에러 경로의 handlingMessageErrors() (line 290-313)에서는 SQS 메시지 삭제만 수행하고 workspace cleanup을 호출하지 않음. (2) Datadog 로그에서 job 1012095이 20:03:37 최초 수신 → 20:09:51 workspace cleanup → 21:15:48 SQS 재수신 확인. 재수신 시 copySourceDir() (file-system.manager.ts:413-427)가 EFS에서 workspace를 재생성. (3) 재처리가 21:26:59에 RESC10000 에러로 실패 → handlingMessageErrors() 호출 → cleanup 미수행 → stale workspace 잔존 → 21:27:00에 job 1012296이 영향받음.

변경 사항:

  • Root Cause Summary: "이전 작업이 남긴 것"을 구체화 — SQS 재수신에 의한 workspace 재생성 + 에러 경로의 cleanup 누락이 stale workspace 잔존 원인임을 명시
  • Technical Analysis > Code Path: 새 섹션 "5. Workspace Cleanup 실패 경로" 추가 — cleanUpAnythingRelatedModel()이 성공 경로에서만 호출되는 코드 구조, handlingMessageErrors()의 cleanup 누락, SQS 재수신에 의한 copySourceDir() 재실행 경로 분석
  • Log Evidence: workspace cleanup 타임라인 추가 — job 1012095의 최초 실행(20:03:37) → cleanup(20:09:51) → SQS 재수신(21:15:48) → RESC10000 에러(21:26:59) → job 1012296 실행(21:27:00) 시퀀스
  • Fix Recommendation: "에러 경로에서 workspace cleanup 추가" (base-service.ts:290-313)를 즉시 조치 #1으로 추가. "job 시작 시 stale workspace 감지" (postprocessor-service.ts:80-83)를 즉시 조치 #3으로 추가. 장기 개선에 "SQS 멱등성 보장"과 "workspace 경로를 job_id 기반으로 변경" 추가

추가 조사 내용:

  • cupixworks 레포: BaseService (base-service.ts) 전체 job lifecycle 분석 — checkingQueue()runByMessages()runByMessage()run()cleanUpAnythingRelatedModel() 성공 경로 및 handlingMessageErrors() 에러 경로 분석
  • cupixworks 레포: PostprocessorService (postprocessor-service.ts) — createCPCaptureByJob(), setModelWorkspace() 호출 순서 분석
  • cupixworks 레포: FileSystemManager.copySourceDir() (file-system.manager.ts:413-445) — EFS에서 workspace로의 파일 복사 경로 분석
  • cupixworks 레포: CPUtils.deleteFolderRecursive() (cputils.ts:78-90) — 실제 workspace 삭제 구현 확인
  • cupixworks 레포: Constants (shared-config/src/constants.ts) — DefaultWorkspacePath = '/tmp/workspace', MaxCountWaitedToStopTask, CheckQueueInterval 등 agent lifecycle 상수 확인
  • Datadog 로그: cleanUpAnythingRelatedModel + 680039 → 20:09:51 cleanup 1건 확인. job 1012095 로그 → 20:03:37 최초 수신, 21:15:48 재수신, 21:26:59 RESC10000 에러 확인. handlingMessageErrors 로그 → modelId 1012095의 에러 처리 확인 (workspace cleanup 없음). runByMessage 로그 → job 1012296이 21:27:00에 실행된 것 확인