ES /docs

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.0api-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#

  1. 2026-06-12 20:43:24~20:43:30 KST — pointcloud 1144140 의 octree/CPC mesh 업로드 단계 정상 완료 (Rails API 200 응답 다수).
  2. 2026-06-12 20:44:03 KST — potree-agent 가 SQS 메시지 수신, PotreeService::runByMessage | id: 1144140 info 로그.
  3. 2026-06-12 20:44:03 KSTrequest.get(http://api-tesla.cupix.internal/api/v1/pointclouds/1144140/download) 호출 시작.
  4. 2026-06-12 20:46:04 KST — 약 121초 후 ELB 가 504 Gateway Time-out 응답 (server: awselb/2.0). PotreeService::downloadFile error 로그 발생 → handlingMessageErrorsundefined response 로 분류해 메시지 삭제 없이 throw.
  5. 2026-06-12 20:47:15 KST — SQS 메시지 재수신 후 동일 download 엔드포인트가 [302] 로 정상 redirect (Rails 응답 정상화).

Error Log#

Datadog Logs

text
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 다운로드를 시작한다.

applications/agents/packages/cupix-tesla-potree-agent/src/potree-service.ts:200-214typescript
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.getresponse 이벤트에서 statusCode !== 200 분기로 진입해 reject. 504 응답은 ELB 가 만들어준 HTML 본문(text/html, length 132) 이라 JSON 파싱 불가.

applications/agents/packages/cupix-tesla-potree-agent/src/potree-service.ts:552-564typescript
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));
    });

에러 처리 — handlingMessageErrorsgetApiErrorToDeleteMessage 를 통해 응답을 분류한다. 504 응답 본문이 HTML 이라 response.body.result 가 없어 undefined response 로 분류되며, ApproximateReceiveCount === 1 이라 checkReceiveCountToDeleteMessage 가 false 를 반환해 메시지를 삭제하지 않고 SQS visibility timeout 만료 후 재처리된다.

applications/agents/packages/cupix-tesla-potree-agent/src/potree-service.ts:465-489typescript
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 는 없다.

app/controllers/concerns/single_resourcable_controller.rb:10-19ruby
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 기준):

text
service:cupixworks-any-potree-agent status:error @environment:production "PotreeService::downloadFile"

Pointcloud 1144140 관련 모든 potree-agent 로그:

text
service:cupixworks-any-potree-agent (1144140 OR "1144140")

핵심 로그 (potree-agent):

text
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 임을 명확히 확인):

json
{
  "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분 뒤 재시도는 정상 처리됨:

text
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 에러 (재발 빈도 평가용):

text
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 와 Rails request.duration p99 메트릭에 알람 추가.

Monitoring#

cupixworks-any-potree-agent 의 download 5xx/timeout 빈도를 추적하는 timeseries 쿼리:

text
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):

text
sum:aws.applicationelb.httpcode_elb_5xx{loadbalancer:*api-tesla*}.as_count()

Rails download 엔드포인트 응답 시간 p95 (Rails APM):

text
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.