TransferManager::uploadFile | path: /tmp/workspace/nhc-sa.675622_result/nhc-sa.675622_1264112.cpc, e
RCA: TransferManager::uploadFile | EPIPE error during CPC file upload
Error Log#
TransferManager::uploadFile | path: /tmp/workspace/nhc-sa.675622_result/nhc-sa.675622_1264112.cpc, error: {"stack":"Error: write EPIPE
at WriteWrap.onWriteComplete [as oncomplete] (node:internal/stream_base_commons:95:16)","message":"write EPIPE","errno":-32,"code":"EPIPE","syscall":"write"}
Impact#
- Service:
cupixworks-capture-3dreconstruction-instance - Team: nhc-sa
- 발생 횟수: 1
- 최초 발생: 2026-04-07T00:23:49.478Z
- 최근 발생: 2026-04-07T00:23:49.478Z
Root Cause Summary#
3D Reconstruction agent가 CPC 파일(nhc-sa.675622_1264112.cpc)을 업로드하는 중 upstream 서버(또는 로드밸런서/프록시)가 연결을 조기에 종료하여 EPIPE 에러가 발생했습니다. 동일 시각에 502 Bad Gateway 응답도 기록되어, 업로드 대상 서버의 일시적 장애 또는 요청 타임아웃이 원인으로 판단됩니다. uploadFile 메서드가 fs.readFileSync()로 파일 전체를 메모리에 로드한 후 request.put()으로 전송하는 구조이므로, 대용량 파일의 경우 전송 시간이 길어져 upstream 타임아웃에 취약합니다. 이 EPIPE 에러는 2026-04-02에도 동일한 패턴으로 발생한 반복적 문제이며, 최근 1주간 uploadFile 관련 에러가 20건 이상 기록되었습니다(대부분 500 Internal Server Error).
Technical Analysis#
Code Path#
- Entry point:
transfer.manager.ts:86—uploadFile메서드 request.put()에fs.readFileSync(path)로 읽은 파일 전체를 body로 전달:
// applications/agents/packages/cupix-capture-3d-reconstruction-agent/src/manager/transfer.manager.ts:86-108
uploadFile = (url: string, path: string, headers: any): Promise<void> => new Promise((resolve, reject) => {
logger.debug('TransferManager::uploadFile | begin path: %s, url: %s, size: %d', path, url, CPUtils.getFileSize(path));
const sendReq = request.put(url, {
headers: headers,
body: fs.readFileSync(path) // 파일 전체를 메모리에 동기적으로 로드
});
sendReq
.on('response', async res => {
if (res.statusCode === 200) {
// ...
} else {
logger.error('TransferManager::uploadFile | response path: %s, code: %d, message: %s', path, res.statusCode, res.statusMessage);
reject(this.cupixAuth.handleError(res)); // 502 응답 시 이 경로
}
})
.on('error', err => {
logger.error('TransferManager::uploadFile | path: %s, error: %s', path, JSON.stringify(err, Object.getOwnPropertyNames(err)));
reject(this.cupixAuth.handleError(err)); // EPIPE 에러 시 이 경로
});
});
-
request.put()의error이벤트와response이벤트가 동시에 발생:error이벤트: EPIPE — 서버가 연결을 닫은 후 클라이언트가 write 시도response이벤트: 502 Bad Gateway — upstream 서버의 응답
-
CupixAuth::handleError(cupix-auth.ts:39-54)에서 EPIPE 에러 처리:
// applications/agents/packages/api/src/authentication/cupix-auth.ts:39-54
handleError = (ec: any): any => {
const response = ec && CPUtils.isJsonString(ec) ? JSON.parse(ec) : ec.response;
if (response != undefined) {
// ...
} else {
// EPIPE 에러는 response가 없으므로 이 경로로 진입
logger.warn('CupixAuth::handleError | Undefined response: %s', JSON.stringify(ec, Object.getOwnPropertyNames(ec)));
}
return ec; // 원본 에러 객체를 그대로 반환
};
- Retry 로직 (
transfer.manager.ts:169-172):
// applications/agents/packages/cupix-capture-3d-reconstruction-agent/src/manager/transfer.manager.ts:169-172
private checkStatusCode = (error: any): boolean => {
if (error?.statusCode != undefined && error.statusCode > 400 && error.statusCode < 500) return false;
return true;
};
-
EPIPE 에러 객체에는
statusCode가 없으므로(errno: -32,code: "EPIPE")checkStatusCode는true를 반환하여 retry가 허용됩니다.MaxRetries는 5,RetryInterval은 10초입니다 (shared-config/src/constants.ts:8-9). -
기대 동작: 파일 업로드 → 200 응답 → 완료
-
실제 동작: 파일 업로드 중 upstream 서버 연결 끊김 →
error이벤트(EPIPE) +response이벤트(502) 동시 발생 →reject()가 두 번 호출될 수 있음 (Promise는 첫 번째만 처리)
Log Evidence#
사용한 Datadog 쿼리:
service:cupixworks-capture-3dreconstruction-instance status:error "TransferManager::uploadFile"
Time range: 2026-04-06T23:00:00Z to 2026-04-07T02:00:00Z
service:cupixworks-capture-3dreconstruction-instance "EPIPE"
Time range: 2026-03-25T00:00:00Z to 2026-04-08T00:00:00Z
service:cupixworks-capture-3dreconstruction-instance "502"
Time range: 2026-04-01T00:00:00Z to 2026-04-08T00:00:00Z
핵심 로그 타임라인 (2026-04-07 09:23:49 KST, 모두 동일 시각):
- EPIPE 에러 (error level):
TransferManager::uploadFile | path: /tmp/workspace/nhc-sa.675622_result/nhc-sa.675622_1264112.cpc, error: {"stack":"Error: write EPIPE\n at WriteWrap.onWriteComplete [as oncomplete] (node:internal/stream_base_commons:95:16)","message":"write EPIPE","errno":-32,"code":"EPIPE","syscall":"write"}
- 502 Bad Gateway 응답 (error level):
TransferManager::uploadFile | response path: /tmp/workspace/nhc-sa.675622_result/nhc-sa.675622_1264112.cpc, code: 502, message: Bad Gateway
- CupixAuth의 EPIPE 처리 (warn level):
CupixAuth::handleError | Undefined response: {"stack":"Error: write EPIPE\n at WriteWrap.onWriteComplete [as oncomplete] (node:internal/stream_base_commons:95:16)","message":"write EPIPE","errno":-32,"code":"EPIPE","syscall":"write"}
- CupixAuth의 502 처리 (warn level):
CupixAuth::handleError | Undefined response: {"statusCode":502,"request":{"method":"PUT"}}
과거 동일 패턴 발생 (2026-04-02 19:06:13 KST):
TransferManager::uploadFile | path: /tmp/workspace/gad.672414_result/gad.672414_1257830.cpc, error: {"stack":"Error: write EPIPE\n at WriteWrap.onWriteComplete [as oncomplete] (node:internal/stream_base_commons:95:16)","message":"write EPIPE","errno":-32,"code":"EPIPE","syscall":"write"}
최근 1주간 uploadFile 에러 패턴:
- EPIPE + 502 Bad Gateway: 2건 (2026-04-02, 2026-04-07)
- 500 Internal Server Error: 18건 이상 (다양한 팀의
.cpc,_mesh.cpc,_octree.bin파일)
Fix Recommendation#
즉시 조치 (Critical)#
transfer.manager.ts:86-108의uploadFile메서드에서error와response이벤트가 동시에 발생할 때reject()가 두 번 호출되는 문제 방지. Promise의 resolve/reject 상태를 추적하여 중복 호출을 막아야 합니다.- 502 Bad Gateway는 upstream 서버의 일시적 장애이므로, 서버 인프라(로드밸런서, API 서버) 측에서 업로드 endpoint의 타임아웃 설정과 가용성을 점검해야 합니다.
단기 개선 (1주 이내)#
transfer.manager.ts:90에서fs.readFileSync(path)로 파일 전체를 메모리에 로드하는 대신,fs.createReadStream(path)을 사용하여 streaming 업로드로 전환. 이렇게 하면 대용량 파일의 메모리 사용량이 줄고, upstream 서버와의 연결 유지 시간이 단축됩니다.- retry 로직에 exponential backoff 적용을 고려. 현재 고정 10초 간격(
RetryInterval = 10000)으로 재시도하는데, upstream 서버 장애 시에는 점진적으로 대기 시간을 늘리는 것이 효과적입니다.
장기 개선 (재발 방지)#
request라이브러리는 deprecated 상태이므로,axios,undici, 또는 Node.js 내장fetch로 HTTP 클라이언트를 마이그레이션. 이들은 streaming, timeout, abort signal 등 현대적 기능을 더 잘 지원합니다.- 업로드 대상이 S3 presigned URL인 경우, AWS SDK의 multipart upload를 사용하면 대용량 파일 업로드의 신뢰성이 크게 향상됩니다.
- 업로드 실패 시 해당 capture/pointcloud의 상태를 적절히 업데이트하여, 후속 파이프라인에서 실패를 감지하고 재처리할 수 있도록 해야 합니다.
Monitoring#
uploadFile에러 발생 빈도 모니터링:
service:cupixworks-capture-3dreconstruction-instance status:error "TransferManager::uploadFile"
- EPIPE 에러 전용 알림:
service:cupixworks-capture-3dreconstruction-instance "EPIPE"
- 502/500 응답 코드별 추세 모니터링:
service:cupixworks-capture-3dreconstruction-instance status:error "uploadFile" ("502" OR "500")
Risk Assessment#
- Risk level: medium
- 예상 복잡도: standard
- 현재 retry 로직(5회, 10초 간격)이 작동하여 일시적 502의 경우 자동 복구될 수 있으나, 반복적 서버 장애 시에는 upload가 최종 실패합니다. 최근 1주간 upload 관련 에러가 20건 이상으로, 서버 측 안정성 문제가 병행되고 있어 클라이언트 측 개선만으로는 완전한 해결이 어렵습니다.