ES /docs

BaseService::handlingMessageErrors | Error and message object - {"error":{"errno":-110,"code":"ETIME

RCA: BaseService::handlingMessageErrors | ETIMEDOUT during thumbnail upload

Error Log#

Datadog Logs

text
BaseService::handlingMessageErrors | Error and message object - {"error":{"errno":-110,"code":"ETIMEDOUT","syscall":"read"},"sqsMessage":{"MessageId":"61d03ebd-9499-4634-ab52-276fba55406b","Attributes":{"ApproximateReceiveCount":"1"}}}

Impact#

  • Service: cupixworks-any-thumbnail-agent
  • Team: hassan-allam
  • 발생 횟수: 1
  • 최초 발생: 2026-04-09T10:55:13.144Z
  • 최근 발생: 2026-04-09T10:55:13.144Z

Root Cause Summary#

cupix-tesla-thumbnail-agent가 eu-central-1 리전의 호스트 ip-10-1-40-3에서 asset bv6n1yt7bmvp에 대한 썸네일 이미지를 업로드하는 도중 TLS 연결의 read syscall에서 타임아웃이 발생했습니다. ImageCPObject::uploadFile 메서드는 request.put()을 호출할 때 timeout 옵션을 설정하지 않으며 retry 로직도 없어, 네트워크 일시적 장애 시 단일 시도 실패로 에러가 전파됩니다. SQS 메시지의 ApproximateReceiveCount가 1이므로 첫 번째 처리 시도에서 발생한 일시적 네트워크 장애이며, SQS의 visibility timeout 이후 자동 재시도됩니다.

Technical Analysis#

Code Path#

  • Entry point: BaseService::checkingQueue() (base/src/base-service.ts:110) — SQS 큐에서 메시지를 수신하고 처리 루프를 시작합니다.
typescript
// applications/agents/packages/base/src/base-service.ts:145-151
private runByMessages = async (): Promise<void> => {
    logger.debug('BaseService::runByMessages | begin');
    for await (const message of this.messageList) {
        await this.runByMessage(message);
    }
    logger.debug('BaseService::runByMessages | end');
};
  • Message processing: BaseService::runByMessage() (base/src/base-service.ts:153-191) — 메시지 body를 파싱하고 인증 후 ThumbnailService.run()을 호출합니다.
typescript
// applications/agents/packages/base/src/base-service.ts:168-180
await TraceUtils.activateSpan(span, async () => {
    this.setLogMeta(msgObject);
    logger.info('BaseService::runByMessage | id: %d', targetId);
    await this.authenticateByMessage(msgObject);
    await this.run(targetId, msgObject);  // ThumbnailService.run() 호출
    await this.cleanUpAnythingRelatedModel();
    await this.deleteByMessage(message);
});
  • Failure point: ImageCPObject::uploadFile() (cupix-tesla-thumbnail-agent/src/model/common/image-cpobject.ts:93-117) — 파일 업로드 시 request.put() 호출에 timeout 설정이 없어 TLS read에서 ETIMEDOUT 발생.
typescript
// cupix-tesla-thumbnail-agent/src/model/common/image-cpobject.ts:96-116
const sendReq = request.put(url, {
    headers: headers,
    body: fs.readFileSync(filePath)  // timeout 옵션 없음
});
sendReq
    .on('complete', async () => {
        await CPUtils.sleep(100);
        resolve();
    })
    .on('response', res => {
        if (res.statusCode !== 200) {
            reject(cupixAuth.handleError(res));
        }
    })
    .on('error', err => {
        logger.error('ImageCPObject::uploadFile | path: %s, error: %s', path, JSON.stringify(err));
        reject(cupixAuth.handleError(err));  // ETIMEDOUT 에러가 여기서 reject
    });
  • Error classification: BaseService::getApiErrorToDeleteMessage() (base/src/base-service.ts:245-247) — errno, code, syscall 속성이 있는 시스템 에러로 인식하여 undefined를 반환합니다. 이로 인해 SQS 메시지가 삭제되지 않고 큐에 남아 재처리됩니다.
typescript
// applications/agents/packages/base/src/base-service.ts:245-247
if (error.errno != undefined && error.code != undefined && error.syscall != undefined) {
    logger.warn('BaseService::getApiErrorToDeleteMessage | nodejs common system error', error);
    return;  // undefined 반환 → 메시지 삭제하지 않음
}
  • 기대 동작: 업로드 실패 시 retry 로직이 동작하여 일시적 네트워크 장애를 자동 복구해야 함.
  • 실제 동작: retry 없이 단일 시도 실패로 에러 전파. SQS의 visibility timeout 기반 재시도에만 의존.

Log Evidence#

에러 발생 시점에 4개의 로그가 기록되었습니다 (2ms 이내):

Datadog 검색 쿼리:

text
service:cupixworks-any-thumbnail-agent status:(error OR warn) @environment:production

1. warn 로그 (10:55:13.142Z) — CupixAuth::handleError:

text
CupixAuth::handleError | Undefined response: {"stack":"Error: read ETIMEDOUT\n    at TLSWrap.onStreamRead (node:internal/stream_base_commons:218:20)\n    at TLSWrap.callbackTrampoline (node:internal/async_hooks:130:17)","message":"read ETIMEDOUT","errno":-110,"code":"ETIMEDOUT","syscall":"read"}

2. error 로그 (10:55:13.142Z) — ImageCPObject::uploadFile:

text
ImageCPObject::uploadFile | path: { default: [Object], resolve: [Getter], normalize: [Getter], isAbsolute: [Getter], join: [Getter], relative: [Getter], toNamespacedPath: [Getter], dirname: [Getter], basename: [Getter], extname: [Getter], format: [Getter], parse: [Getter], matchesGlob: [Getter], sep: [Getter], delimiter: [Getter], win32: [Getter], posix: [Getter], _makeLong: [Getter] }, error: {"errno":-110,"code":"ETIMEDOUT","syscall":"read"}

참고: path 파라미터에 파일 경로 대신 Node.js path 모듈 객체가 출력되고 있습니다. 이는 uploadFile의 error 핸들러에서 로깅할 때 path 변수가 import된 path 모듈을 참조하는 스코핑 문제로 보입니다.

3. warn 로그 (10:55:13.144Z) — raw error:

text
read ETIMEDOUT
  • Stack trace: Error: read ETIMEDOUT at TLSWrap.onStreamRead (node:internal/stream_base_commons:218:20)

4. error 로그 (10:55:13.144Z) — BaseService::handlingMessageErrors:

text
BaseService::handlingMessageErrors | Error and message object - {"error":{"errno":-110,"code":"ETIMEDOUT","syscall":"read"},"sqsMessage":{"MessageId":"61d03ebd-9499-4634-ab52-276fba55406b","Attributes":{"ApproximateReceiveCount":"1"}}}

컨텍스트 정보 (로그 메타데이터):

  • Host: ip-10-1-40-3.eu-central-1.compute.internal
  • Region: eu-central-1
  • User: paul.esguerra@stantec.com (id: 3775)
  • Team: hassan-allam (id: 105)
  • Asset key: bv6n1yt7bmvp
  • Session: 5ebb9eb99cc1c2d0ef9c627c661eebbac5f3566c

7일간 에러 빈도 분석:

text
service:cupixworks-any-thumbnail-agent status:error @environment:production

7일간 총 24개 에러 중 ETIMEDOUT은 이 1건만 발견됨. 나머지 에러는 HTTP 500 (Internal Server Error), 404 (Not Found), 403 (Bim/Floorplan not found) 등 API 레벨 에러. 동일 시간대 다른 호스트에서는 정상적으로 SQS 메시지를 처리 중이었으며 (200+ info 로그 확인), 이 ETIMEDOUT은 ip-10-1-40-3 호스트에서만 발생한 일시적 네트워크 장애입니다.

Fix Recommendation#

즉시 조치 (Critical)#

별도 즉시 조치 불필요. SQS 메시지가 삭제되지 않고 큐에 남아있으므로 (ApproximateReceiveCount: 1), SQS visibility timeout 이후 자동으로 재처리됩니다. 단발성 네트워크 장애이며 서비스 전체에는 영향이 없습니다.

단기 개선 (1주 이내)#

  1. ImageCPObject::uploadFile에 timeout 옵션 추가 (cupix-tesla-thumbnail-agent/src/model/common/image-cpobject.ts:96-98)

    • request.put()timeout 옵션 (예: 30초)을 설정하여 무한 대기를 방지해야 합니다.
    • 현재는 timeout 설정이 없어 TLS 연결이 끊어질 때까지 무기한 대기합니다.
  2. 업로드 retry 로직 추가 (image-cpobject.ts:93-117)

    • 다른 agent에서 사용하는 TransferManager의 retry 패턴 (cupix-capture-3d-reconstruction-agent/src/manager/transfer.manager.ts:175-193)을 참고하여, ETIMEDOUT 같은 일시적 에러에 대한 retry (예: 3회, exponential backoff)를 구현해야 합니다.
    • 현재는 SQS의 재처리에만 의존하고 있어 불필요한 지연이 발생합니다.
  3. path 변수 스코핑 문제 수정 (image-cpobject.ts:115)

    • error 핸들러의 logger.error('... path: %s', path, ...) 에서 path가 파일 경로가 아닌 Node.js path 모듈을 참조하고 있습니다. filePath 파라미터를 사용하도록 수정해야 합니다.

장기 개선 (재발 방지)#

  1. 공통 HTTP 클라이언트 모듈 도입 — 모든 agent가 동일한 timeout, retry, connection pooling 설정을 사용하도록 base 패키지에 공통 HTTP 클라이언트를 구현. 현재 ImageCPObject, TransferManager 등이 각각 독립적으로 request 라이브러리를 사용하고 있어 설정이 일관되지 않음.

  2. request 라이브러리 교체 검토request 라이브러리는 deprecated 상태. axios 또는 undici 등 유지보수되는 HTTP 클라이언트로 마이그레이션을 검토. 내장 retry, timeout, keep-alive 지원이 더 우수합니다.

Monitoring#

  • ETIMEDOUT 에러 발생 빈도 모니터링:
text
service:cupixworks-any-thumbnail-agent "ETIMEDOUT" status:error @environment:production
  • 업로드 실패율 전체 추적:
text
service:cupixworks-any-thumbnail-agent "ImageCPObject::uploadFile" status:error @environment:production
  • SQS 메시지 재처리 횟수 (높은 ApproximateReceiveCount) 알림 추가 검토

Risk Assessment#

  • Risk level: low
  • 예상 복잡도: standard
  • 단발성 네트워크 장애이며 SQS 재처리로 자동 복구됩니다. 7일간 동일 에러 재발 없음. 단, timeout/retry 부재는 잠재적 위험이므로 단기 개선을 권장합니다.