ES /docs

ImageCPObject.downloadFile() missing timeout — hangs on TLS stall

RCA: ImageCPObject::downloadFile ETIMEDOUT

Overview#

What Happened#

2026-05-22 06:26 UTC에 cupixworks-any-thumbnail-agent 서비스에서 Asset 썸네일 생성을 위한 파일 다운로드 중 TCP read timeout(ETIMEDOUT)이 발생했다. 다운로드가 06:10:38에 시작되어 약 15분 30초 후에 타임아웃으로 실패했으며, SQS 재시도 시에도 업로드 단계에서 동일한 ETIMEDOUT이 재발했다.

Quick Facts#

Field Value
exception.class ETIMEDOUT
exception.message ImageCPObject::downloadFile | path: /tmp/workspace/8mqd7168rx4l/8mqd7168rx4l.jpg, error: {"errno":-110,"code":"ETIMEDOUT","syscall":"read"}
top_frame image-cpobject.ts:downloadFile
runtime Node.js (ECS Fargate)
env production, eu-central-1

Affected Teams#

Team / Domain Error Count Impact
hassan-allam 1 Asset 썸네일 생성 지연 (SQS 재시도로 최종 실패)

Timeline#

  1. 06:10:38Z — SQS 메시지 수신, Asset 8mqd7168rx4l 썸네일 작업 시작
  2. 06:10:38Zapi-tesla.cupix.internal로 다운로드 요청 시작
  3. 06:26:09Z — TCP read timeout 발생 (ETIMEDOUT, ~15.5분 경과)
  4. 08:14:44Z — SQS 재시도 (2차): 다운로드 성공, 썸네일/커버 생성 성공
  5. 08:32:19Z — 업로드 단계에서 동일한 ETIMEDOUT 발생, 작업 최종 실패

Error Log#

Datadog Logs

text
ImageCPObject::downloadFile | path: /tmp/workspace/8mqd7168rx4l/8mqd7168rx4l.jpg, error: {"errno":-110,"code":"ETIMEDOUT","syscall":"read"}

Impact#

  • Service: cupixworks-any-thumbnail-agent
  • Team: hassan-allam
  • 발생 횟수: 1
  • 최초 발생: 2026-05-22T06:26:09.786Z
  • 최근 발생: 2026-05-22T06:26:09.786Z

Root Cause Summary#

ImageCPObject.downloadFile() 메서드에서 request.get() 호출 시 timeout 옵션이 설정되어 있지 않아 OS 레벨 TCP timeout에 전적으로 의존하고 있다. 내부 API 서버(api-tesla.cupix.internal)와의 연결에서 데이터 전송 중 TLS 스트림이 멈췄을 때, 합리적인 시간 내에 실패 처리하지 못하고 ~15분간 대기 후 커널 TCP timeout으로 실패했다. 재시도 로직도 없어 일시적 네트워크 불안정에 취약한 구조이다.

Technical Analysis#

Code Path#

  • Entry point: BaseService.runByMessage() — SQS 메시지 수신 및 작업 시작
  • ThumbnailService.newModel() — Asset 모델 생성 (id: NaN 로깅 버그 — 문자열 ID를 parseInt 처리)
  • ImageCPObject.downloadOriginal() — 다운로드 URL 구성 및 downloadFile 호출
  • Failure point: image-cpobject.ts:downloadFilerequest.get() 호출에서 timeout 없이 대기
applications/agents/packages/cupix-tesla-thumbnail-agent/src/model/common/image-cpobject.ts:53-91typescript
private downloadFile = (url: string, filePath: string): Promise<void> => new Promise((resolve, reject) => {
    const cupixAuth = this.cupixAuth;
    cupixAuth.checkToken()
        .then(() => {
            const fileStream = fs.createWriteStream(filePath);
            const sendReq = request.get(url, {
                headers: {
                    'X-CUPIX-AUTH': cupixAuth.accessToken
                }
                // timeout 옵션 없음 — OS TCP timeout에 의존
            });
            // ...
            sendReq
                .on('error', err => {
                    logger.error('ImageCPObject::downloadFile | path: %s, error: %s', filePath, JSON.stringify(err));
                    reject(cupixAuth.handleError(err));
                });
        });
});

기대 동작: HTTP GET 요청에 적절한 timeout이 설정되어, 응답이 지연될 경우 빠르게 실패 후 재시도. 실제 동작: timeout 미설정으로 인해 커널 TCP read timeout(~15.5분)까지 무한 대기 후 ETIMEDOUT 발생.

Log Evidence#

사용한 Datadog 쿼리:

text
service:cupixworks-any-thumbnail-agent status:error @environment:production "ImageCPObject::downloadFile"
text
service:cupixworks-any-thumbnail-agent @environment:production "8mqd7168rx4l"

핵심 로그 타임라인:

text
06:10:38.521Z - BaseService::runByMessage | id: NaN
06:10:38.579Z - CupixAuth::setSession | session_id: 2fb0e3db...
06:10:38.581Z - ThumbnailService::newModel | model id: NaN, type: Asset
06:10:38.622Z - ImageCPObject::downloadOriginal | url: http://api-tesla.cupix.internal/api/v1/assets/8mqd7168rx4l/download
06:26:09.786Z - ImageCPObject::downloadFile | path: /tmp/workspace/8mqd7168rx4l/8mqd7168rx4l.jpg, error: {"errno":-110,"code":"ETIMEDOUT","syscall":"read"}
06:26:09.787Z - CupixAuth::handleError | Undefined response (TLSWrap.onStreamRead stack trace)
06:26:09.791Z - BaseService::handlingMessageErrors (ApproximateReceiveCount: 1)

2차 시도 (SQS 재시도):

text
08:14:44Z - 다운로드 성공 (~1초 소요)
08:14:45-50Z - 썸네일 생성 성공 (38,944 bytes, 5,358ms)
08:14:52-54Z - 커버 생성 성공 (1,691,704 bytes, 2,396ms)
08:32:19.752Z - ImageCPObject::uploadFile ETIMEDOUT (업로드 단계에서 동일 에러 재발)

같은 시간대 서비스 전반의 네트워크 불안정 증거:

text
04:29-04:58Z - 다수의 504 Gateway Timeout (ImageCPObject::putThumbnail, nginx)
06:03-06:04Z - ImageCPObject::uploadFile HTTP 500 Internal Server Error

Hypotheses Considered#

# Hypothesis Evidence for Evidence against Verdict
H1 timeout 미설정으로 인한 TCP read 무한 대기 request.get() 호출에 timeout 옵션 없음 (image-cpobject.ts:60). 15.5분간 대기 후 OS TCP timeout 발생. request 라이브러리 문서상 timeout 미설정 시 OS 의존 확인. Confirmed
H2 Asset 파일 크기가 너무 커서 다운로드 실패 첫 시도에서 15분 대기 2차 시도에서 동일 파일을 ~1초에 다운로드 성공. 파일 크기 문제가 아닌 네트워크 연결 문제 Rejected
H3 내부 API 서버(api-tesla.cupix.internal) 가용성 문제 같은 시간대 504, 500 에러 다수 발생. 다른 작업들은 정상 완료됨 대부분의 동시 작업이 정상 처리됨 — 전면 장애가 아닌 간헐적 연결 문제 Inconclusive

Fix Recommendation#

즉시 조치 (Critical)#

  • image-cpobject.tsdownloadFile 메서드에서 request.get() 호출에 timeout 옵션 추가 (예: timeout: 60000 — connection + read 각각 60초)
  • 동일 파일의 uploadFile 메서드에도 동일하게 timeout 설정

단기 개선 (1주 이내)#

  • downloadFileuploadFile에 재시도 로직 추가 (exponential backoff, 최대 3회)
  • CupixAuth.retryable() 헬퍼가 이미 존재하므로 이를 활용
  • id: NaN 로깅 버그 수정 — Asset의 문자열 ID를 parseInt 없이 그대로 로깅

장기 개선 (재발 방지)#

  • deprecated된 request 라이브러리(v2.88.2)를 axios 또는 undici로 마이그레이션 (코드베이스의 CupixAuth는 이미 axios 사용 중)
  • 다운로드/업로드 작업에 대한 circuit breaker 패턴 도입
  • ECS Fargate에서 내부 API로의 네트워크 경로 모니터링 추가

Monitoring#

  • 다운로드/업로드 소요 시간 메트릭 추가 및 P99 > 30초 시 알림
  • Datadog 쿼리 예시:
text
service:cupixworks-any-thumbnail-agent status:error "ETIMEDOUT"
text
service:cupixworks-any-thumbnail-agent "downloadFile" @duration:>30000

Risk Assessment#

  • Risk level: low
  • 예상 복잡도: standard — timeout 추가는 단순하지만 request 라이브러리 마이그레이션은 테스트 필요