PotreeService::downloadFile | response path: /tmp/workspace/1144140/1144140.cpc, code: 504, message:
RCA: PotreeService::downloadFile 504 Gateway Time-out
Overview#
What Happened#
2026-06-12 20:46:04 KST 시점에 cupixworks-any-potree-agent (production, us-west-2) 가 pointcloud 1144140 의 .cpc 원본 파일을 내부 Rails API (http://api-tesla.cupix.internal/api/v1/pointclouds/1144140/download) 에서 다운로드하려다 AWS ELB 로부터 504 Gateway Time-out 응답을 받고 SQS 메시지 처리에 실패했다. 동일 pointcloud 의 재시도는 같은 메시지가 SQS visibility timeout 후 재처리되어 1분 뒤(20:47:15 KST) [302] 로 정상 redirect 되어 성공했다.
Quick Facts#
| Field | Value |
|---|---|
| exception.class | (no thrown class — logger.error path) |
| exception.message | PotreeService::downloadFile | response path: /tmp/workspace/1144140/1144140.cpc, code: 504, message: Gateway Time-out |
| top_frame | packages/cupix-tesla-potree-agent/src/potree-service.ts:555 |
| upstream | awselb/2.0 → api-tesla.cupix.internal (Rails API) |
| env | production, us-west-2 |
| tenant | cupix |
Affected Teams#
| Team / Domain | Error Count | Impact |
|---|---|---|
| flintco / pointcloud processing | 1 | pointcloud 1144140 의 potree 변환이 한 번 실패. SQS 재처리로 자동 복구. 사용자가 본 영향 없음. |
Timeline#
- 2026-06-12 20:43:24~20:43:30 KST — pointcloud
1144140의 octree/CPC mesh 업로드 단계 정상 완료 (Rails API 200 응답 다수). - 2026-06-12 20:44:03 KST — potree-agent 가 SQS 메시지 수신,
PotreeService::runByMessage | id: 1144140info 로그. - 2026-06-12 20:44:03 KST —
request.get(http://api-tesla.cupix.internal/api/v1/pointclouds/1144140/download)호출 시작. - 2026-06-12 20:46:04 KST — 약 121초 후 ELB 가
504 Gateway Time-out응답 (server: awselb/2.0).PotreeService::downloadFileerror 로그 발생 →handlingMessageErrors가undefined response로 분류해 메시지 삭제 없이 throw. - 2026-06-12 20:47:15 KST — SQS 메시지 재수신 후 동일
download엔드포인트가[302]로 정상 redirect (Rails 응답 정상화).
Error Log#
PotreeService::downloadFile | response path: /tmp/workspace/1144140/1144140.cpc, code: 504, message: Gateway Time-out
Impact#
- Service:
cupixworks-any-potree-agent - Team: flintco
- 발생 횟수: 1
- 최초 발생: 2026-06-12 20:46:04 KST
- 최근 발생: 2026-06-12 20:46:04 KST
Root Cause Summary#
Pointcloud 1144140 다운로드 요청이 내부 Rails API (api-tesla.cupix.internal) 앞단의 AWS ELB idle timeout(약 60~120s) 이내에 응답을 받지 못해 ELB 가 클라이언트(potree-agent) 에 504 Gateway Time-out 을 반환했다. 1분 뒤 동일 요청이 [302] 로 정상 redirect 된 것으로 보아 Rails 코드 결함이 아니라 그 시점의 일시적 upstream slowness/saturation 으로 인한 transient 장애이다. potree-agent 의 handlingMessageErrors 는 504 같은 5xx 응답 본문이 JSON 이 아닐 때 undefined response 로 분류해 SQS 메시지를 삭제하지 않으므로 재처리 후 자동 복구되었다.
Technical Analysis#
Code Path#
Entry point — agent 가 SQS 메시지를 받아 pointcloud 다운로드를 시작한다.
if (cpPointcloud.potreeState !== TESLA.PointcloudPotreeState.Uploaded) {
await this.updatePotreeState(targetId, TESLA.PointcloudPotreeState.Processing);
if (cpPointcloud.downloadUrl != undefined && cpPointcloud.originalFilePath != undefined) {
await this.downloadFile(cpPointcloud.downloadUrl, cpPointcloud.originalFilePath);
const entityParameters = await this.loadEntityParameter(cpPointcloud.id);
...
}
}
Failure point — request.get 의 response 이벤트에서 statusCode !== 200 분기로 진입해 reject. 504 응답은 ELB 가 만들어준 HTML 본문(text/html, length 132) 이라 JSON 파싱 불가.
sendReq
.on('response', res => {
if (res.statusCode !== 200) {
logger.error('PotreeService::downloadFile | response path: %s, code: %d, message: %s', path, res.statusCode, res.statusMessage);
reject(cupixAuth.handleError(res));
} else {
sendReq.pipe(fileStream);
}
})
.on('error', err => {
logger.error('PotreeService::downloadFile | path: %s, error: %s', path, JSON.stringify(err));
reject(cupixAuth.handleError(err));
});
에러 처리 — handlingMessageErrors 가 getApiErrorToDeleteMessage 를 통해 응답을 분류한다. 504 응답 본문이 HTML 이라 response.body.result 가 없어 undefined response 로 분류되며, ApproximateReceiveCount === 1 이라 checkReceiveCountToDeleteMessage 가 false 를 반환해 메시지를 삭제하지 않고 SQS visibility timeout 만료 후 재처리된다.
const response = CPUtils.isJsonString(error) ? JSON.parse(error) : error.response;
if (response == undefined) {
logger.warn('PotreeService::getApiErrorToDeleteMessage | undefined response - %s', JSON.stringify(error));
return 'undefined response';
}
const statusCode = response.statusCode ? Number(response.statusCode) : undefined;
...
if (statusCode != undefined && statusCode >= 400 && statusCode <= 500) {
if (statusCode === 401) return;
return errorMsg;
}
return;
참고: 위 분기에서
statusCode >= 400 && statusCode <= 500은< 500의 오타로 보인다 (지원되는 “delete 대상” 의도는 4xx). 504 는 어차피>= 400 && <= 500에 들어가므로 만약 응답이body.result를 가지고 있었다면 즉시 메시지 삭제 처리되어 재시도 불가능했을 것이다. 이번 케이스는 본문이 HTML 이라 우연히undefined response분기를 타고 살아남아 재처리되었다.
기대 동작 vs 실제 동작
- 기대: 다운로드 URL → Rails 컨트롤러
Api::V1::PointcloudsController#download_single_resource→ S3 redirect 302 → 파일 stream. - 실제: ELB 가 약 120초 동안 응답을 받지 못하고 504 반환. Rails 측 access log 에는 해당 요청이 남지 않음(아래 Log Evidence 참조).
Rails 컨트롤러 — 정상 경로는 단순 redirect 이며 자체 무거운 IO 는 없다.
def download_single_resource
if @resource.revision == 0
raise Cupix::Errors::Resource.new(code: 'ENT10011', reason: "Resource does not uploaded: #{@resource.revision}")
else
download_opts = {}
download_opts[:filename] = params[:filename] if params[:filename].present?
redirect_to @resource.download_url(download_opts), allow_other_host: true
end
end
Log Evidence#
Datadog query (cluster URL 기준):
service:cupixworks-any-potree-agent status:error @environment:production "PotreeService::downloadFile"
Pointcloud 1144140 관련 모든 potree-agent 로그:
service:cupixworks-any-potree-agent (1144140 OR "1144140")
핵심 로그 (potree-agent):
2026-06-12 20:44:03 info PotreeService::runByMessage | id: 1144140
2026-06-12 20:46:04 error PotreeService::downloadFile | response path: /tmp/workspace/1144140/1144140.cpc, code: 504, message: Gateway Time-out
2026-06-12 20:46:04 warn PotreeService::getApiErrorToDeleteMessage | undefined response - {...}
2026-06-12 20:46:05 error PotreeService::handlingMessageErrors | Error and message object - {"error":"undefined response","sqsMessage":{"MessageId":"0673bebd-df13-4c5e-b006-ea1a14954af5","Attributes":{"ApproximateReceiveCount":"1"}}}
ELB 504 응답 헤더 (warn 로그에서 추출 — upstream 이 ELB 임을 명확히 확인):
{
"statusCode": 504,
"headers": {
"server": "awselb/2.0",
"date": "Fri, 12 Jun 2026 11:46:04 GMT",
"content-type": "text/html",
"content-length": "132"
},
"request": {
"uri": {
"host": "api-tesla.cupix.internal",
"pathname": "/api/v1/pointclouds/1144140/download"
},
"method": "GET"
}
}
Rails API access log 비교 (service:cupixworks-api "pointclouds/1144140") — 실패 시점(20:46:04)의 download 요청은 access log 에 남지 않았고, 1분 뒤 재시도는 정상 처리됨:
2026-06-12 20:43:24 info [200] PUT /api/v1/pointclouds/1144140/meta/prop
2026-06-12 20:43:25 info [200] PUT /api/v1/pointclouds/1144140/meta/mesh
... (octree/cpc upload 정상 완료) ...
2026-06-12 20:44:06 info [200] PUT /api/v1/pointclouds/1144140
2026-06-12 20:46:51 info [200] GET /api/v1/pointclouds/1144140 (재처리 사이클의 메타 조회)
2026-06-12 20:47:15 info [302] GET /api/v1/pointclouds/1144140/download ← 재처리 시 정상 redirect
실패 요청이 Rails access log 에 남지 않은 점 + ELB
awselb/2.0헤더 + 정확히 ~120s 후 504 발생은 ELB idle/connection timeout 시점에 Rails 백엔드까지 도달하지 못했거나 Rails 가 응답을 ELB timeout 안에 회신하지 못한 패턴과 일치한다.
같은 7일 윈도우의 동일 서비스 다운로드 관련 transient 에러 (재발 빈도 평가용):
2026-06-08 15:20:54 error PotreeService::downloadFile | code: 503, message: Service Unavailable (id 1132267)
2026-06-10 19:50:10 error PotreeService::downloadFile | error: {"code":"ECONNRESET"} (id 1138061)
2026-06-10 22:03:00 error PotreeService::downloadFile | error: {"code":"ETIMEDOUT"} (id 1138413)
2026-06-11 06:02:38 error PotreeService::downloadFile | error: {"code":"ETIMEDOUT"} (id 1139219)
2026-06-12 11:38:39 error PotreeService::downloadFile | error: {"code":"ETIMEDOUT"} (id 1142545)
2026-06-12 11:46:04 error PotreeService::downloadFile | code: 504, Gateway Time-out (id 1144140) ← this cluster
Hypotheses Considered#
| # | Hypothesis | Evidence for | Evidence against | Verdict |
|---|---|---|---|---|
| H1 | 내부 ELB(awselb/2.0) 가 idle/upstream timeout(~120s) 안에 Rails 의 download redirect 응답을 받지 못해 504 를 반환한 transient 장애 |
504 응답 헤더 server: awselb/2.0, content-type: text/html; 20:44:03 요청 시작 → 20:46:04 504 (≈121s); 같은 pointcloud 가 1분 뒤 재시도에서 [302] 정상 응답; Rails access log 에 실패 요청이 남지 않음 |
— | Confirmed |
| H2 | Rails 컨트롤러 download_single_resource 의 코드 결함 (예: 무거운 쿼리, presigned URL 생성 지연) |
redirect_to @resource.download_url(download_opts) 외 무거운 동기 작업 없음 (single_resourcable_controller.rb:10-19); 동일 pointcloud 가 1분 뒤 동일 코드 경로로 정상 응답 |
Rejected | |
| H3 | Pointcloud 1144140 의 데이터 상태 이상 (revision 0, 리소스 없음 등) |
— | 직전 단계에서 octree_upload_url, cpc_mesh_upload_url, check_uploading 등이 모두 200; 재시도 download 가 302 로 성공 |
Rejected |
| H4 | potree-agent 측 인증 토큰 만료 (X-CUPIX-AUTH) |
checkToken() 직후 호출 (potree-service.ts:534) |
실패 응답이 401 이 아닌 504 이고, ELB 가 직접 만든 응답이라 인증 단계 이전임 | Rejected |
| H5 | DNS / VPC 네트워크 단절 | 같은 시간대 동일 pod 의 다른 호출 (api-tesla.cupix.internal/api/v1/pointclouds/1144140 GET 등) 이 정상 응답 |
Rejected |
Fix Recommendation#
즉시 조치 (Critical)#
- 즉시 코드 수정 필요 없음. 단발성(occurrence_count: 1) transient ELB timeout 으로 SQS 재처리에 의해 자동 복구되었음.
errors/클러스터는 frontmatter 만rca_status: completed로 업데이트.
단기 개선 (1주 이내)#
- 로그 레벨 재검토:
PotreeService::downloadFile의 5xx (특히 502/503/504) 응답을error가 아닌warn으로 낮추는 안 검토. 동일 메시지가 SQS 재처리로 회복되는 시나리오라 페이저블한 “error” 보다 “warn” 이 적절하다 (memory: AUTH20022/23 episode 와 같은 패턴). 단,ApproximateReceiveCount가 max 에 도달했을 때만error로 escalate. getApiErrorToDeleteMessage분기 보정 (potree-service.ts:485):statusCode >= 400 && statusCode <= 500→< 500으로 의미를 명확히. 현재는 정확히 500 만 포함(504 는 < 500 아님이지만 인덱스 분기 의도가 모호). 본 인시던트의 root cause 는 아니지만 동일 함수의 잠재 버그.- HTTP client timeout 명시화 (
potree-service.ts:538):request.get(url, { timeout: ... })옵션을 추가해 ELB 의 504 보다 먼저 client side timeout 으로 정의된 retryable error 로 통제.
장기 개선 (재발 방지)#
- 다운로드 경로 재설계:
download엔드포인트는 단순 302 redirect 인데 ELB hop 을 두 번(potree-agent → ELB → Rails → S3 redirect → potree-agent → S3) 거친다. potree-agent 에 S3 presigned URL 을 SQS 메시지에 직접 실어 보내거나cpPointcloud.downloadUrl자체를 presigned 로 받아 ELB hop 을 제거하는 안 검토. - 재시도 정책 표준화:
request(deprecated) →axios/undici로 마이그레이션 +axios-retry등으로 5xx/ETIMEDOUT/ECONNRESET 에 대한 exponential backoff 재시도. 현재는 SQS visibility timeout 에만 의존. - ELB idle timeout / target response 시간 모니터링:
aws.applicationelb.target_5xx와 Railsrequest.duration p99메트릭에 알람 추가.
Monitoring#
cupixworks-any-potree-agent 의 download 5xx/timeout 빈도를 추적하는 timeseries 쿼리:
sum:logs.hits{service:cupixworks-any-potree-agent,@environment:production,status:error,@message:*PotreeService::downloadFile*}.as_count()
Internal ELB 5xx (api-tesla.cupix.internal) 발생 추세 (DataDog AWS integration metric):
sum:aws.applicationelb.httpcode_elb_5xx{loadbalancer:*api-tesla*}.as_count()
Rails download 엔드포인트 응답 시간 p95 (Rails APM):
avg:trace.rack.request.duration{service:cupixworks-api,resource_name:api/v1/pointclouds/*/download}
Datadog timeseries widget 에 그대로 들어가도록 모두 metric query (
sum:/avg:) 로 작성. monitor-only 의| stats,count by(...), threshold suffix 사용 금지.
Risk Assessment#
- Risk level: low — 단발성, 자동 복구, 사용자 영향 없음.
- 예상 복잡도: trivial (즉시 조치 불필요), 단기 개선은 standard.