ES /docs

failed to calculate captured size for Facility ID: 594, error: failed to get captured area - error:

RCA: failed to calculate captured size for Facility ID: 594, error: failed to get captured area - error:

Overview#

What Happened#

2026-07-15 21:33~21:44 KST 사이 CUPIXVISTA 마이그레이션 워커(cupixvista-api-migration-worker)에서 총 8건의 Facility#calculate_captured_size 호출이 실패했다. 실패한 facility는 594, 597, 599, 604, 607, 609, 610, 612이고, downstream voxel service(captured_area Lambda)의 응답이 500 또는 503으로 반환되어 상위 rescue 블록에서 failed to calculate captured size for Facility ID: N, error: failed to get captured area - error: ... 로그가 기록되었다.

본 cluster는 status-board가 동일 svc-scope incident 2026-07-15-svc-cupixvista-api-migration-worker--unknown-1로 묶은 3개 cluster (a558bd06, 3a8d30cc, fe12d217) 중 상위 wrapper 로그에 해당한다. Downstream 근본 원인은 이미 sibling cluster 3a8d30cc-330a-4d70-8ac8-5ce4883bc982의 RCA에서 확정되었다: voxel Lambda가 Athena GetQueryExecution을 sleep 없이 폴링해 ThrottlingException을 유발한 것.

Quick Facts#

Field Value
exception.class rescue in Facility#calculate_captured_size (하위 예외는 Cupix::Errors::System code SYS20000)
exception.message `failed to calculate captured size for Facility ID: {id}, error: failed to get captured area - error: {503
top_frame app/models/concerns/voxel_module.rb:22
downstream AWS Lambda voxel/captured_area (data-pipeline-functions)
downstream_error ThrottlingException on GetQueryExecution (max retries: 3) — 일부는 API Gateway 503
env production, us-west-2, tenant cupix (CUPIXVISTA)

Affected Teams#

Team / Domain Error Count Impact
cupixvista-api-migration-worker 8 (본 cluster) Facility 594, 597, 599, 604, 607, 609, 610, 612의 captured_size migration 실패

동일 svc-scope incident로 묶인 관련 cluster:

  • a558bd06-2374-433b-968a-5e17dfd21a2f — downstream 500 시나리오 wrapper
  • 3a8d30cc-330a-4d70-8ac8-5ce4883bc982 — downstream 503 시나리오 (하위 Cupix::VoxelService.captured_area! 로그)

Timeline#

  1. 2026-07-15 21:33 KST — Facility 594: 503 (cluster first_seen)
  2. 2026-07-15 21:36 KST — Facility 597: 500
  3. 2026-07-15 21:38 KST — Facility 599: 500
  4. 2026-07-15 21:39 KST — Facility 604: 500
  5. 2026-07-15 21:41 KST — Facility 607: 503
  6. 2026-07-15 21:42 KST — Facility 609, 610: 500 (동시)
  7. 2026-07-15 21:44 KST — Facility 612: 503 (cluster last_seen)
  8. 2026-07-15 21:49 KST — voxel Lambda 정상 응답 회복 (sibling RCA 로그 근거)

Error Log#

Datadog Logs

text
failed to calculate captured size for Facility ID: 594, error: failed to get captured area - error: 503 Service Unavailable

Impact#

  • Service: cupixvista-api-migration-worker
  • 발생 횟수: 8
  • 최초 발생: 2026-07-15 21:33 KST
  • 최근 발생: 2026-07-15 21:44 KST
  • 비즈니스 영향: CUPIXVISTA tenant 마이그레이션 대상 8건의 facility가 captured_size 갱신 및 FootprintHistory 레코드 생성에 실패. calculate_captured_size 자체가 rescue로 예외를 삼키고 false를 반환하므로 (voxel_module.rb:19-27) 상위 코드가 별도 재시도를 자동 트리거하지 않을 수 있음 → migration 산출물 stale 상태로 남을 수 있다.

Root Cause Summary#

Downstream voxel service Lambda(services/voxel/lambda/captured_area.py)의 Athena polling 루프에 sleep이 없어 다수 facility 마이그레이션 병렬 처리 시 계정 단위 Athena GetQueryExecution rate limit을 초과, ThrottlingException이 boto3 3회 재시도 이후에도 회복되지 않아 Lambda가 500을 반환하고 일부 요청은 API Gateway/Lambda concurrency 단계에서 503으로 반환된다. 이 downstream 실패가 Cupix::VoxelService.captured_area!에서 Cupix::Errors::System으로 재발생(voxel_service.rb:97)하고, Facility#calculate_captured_size!가 이를 호출하다 실패해 Facility#calculate_captured_size의 wrapper rescue(voxel_module.rb:19-27)에서 본 cluster의 로그 라인이 기록된다. 즉 본 cluster는 downstream Athena throttling의 상위 표현이며 root cause는 sibling 3a8d30cc-330a-4d70-8ac8-5ce4883bc982 RCA와 동일하다.

Technical Analysis#

Code Path#

Entry point: app/workers/refresh_captured_size_worker.rb (Sidekiq worker; sibling RCA 참조)가 Facility#calculate_captured_size!를 호출하는 경로가 아니라, CUPIXVISTA 마이그레이션 워커(service 이름 기준 별도 프로세스)가 Facility#calculate_captured_size(bang 없는 wrapper)를 호출한 것으로 로그의 function: calculate_captured_size 값이 이를 뒷받침한다.

Wrapper rescue (본 cluster의 로그 발생 지점):

app/models/concerns/voxel_module.rb:19-27ruby
def calculate_captured_size(session: nil)
  calculate_captured_size!(session: session)
rescue => e
  Cupix::Logger.error("failed to calculate captured size for #{self.class.name} ID: #{id}, error: #{e.message}", class: self.class.name, function: __method__, model: { id: id, type: self.class.name })

  false
else
  true
end

Bang 버전은 Cupix::VoxelService.calculate_captured_size!로 위임:

app/models/concerns/voxel_module.rb:46-56ruby
def calculate_captured_size!(session: nil)
  if respond_to?(:captured_size)
    if self.instance_of?(::Facility)
      Cupix::VoxelService.calculate_captured_size!(facility: self, session: session)
    elsif self.instance_of?(::Team) || self.instance_of?(::Workspace)
      facilities.find_each do |facility|
        facility.calculate_captured_size!(session: session)
      end
    end
  end
end

Downstream HTTP 호출 실패 지점 (sibling RCA에서 코드/로그로 확인됨):

app/services/cupix/voxel_service.rb:88-99ruby
begin
  response = Cupix::HttpClient.put("#{$CUPIX_VOXEL_SERVICE_URL}/captured_area", body.to_json, headers)

  body = JSON.parse(response.body)

  Cupix::Logger.info("Completed - total: #{body.dig('results', 'total')}, details: #{body.dig('results', 'details')}", class: self.name, function: __method__, facility: { key: params[:facility_key] }, group_by_record: params[:group_by_record])

  body['results']
rescue RestClient::Exception => e
  Cupix::Logger.error("failed to get captured area - error: #{e.message}", class: self.name, function: __method__, facility_key: params[:facility_key], group_by_record: params[:group_by_record])

  raise Cupix::Errors::System.new(code: 'SYS20000', reason: "failed to get captured area - error: #{e.message}")

Downstream root cause (sibling 3a8d30cc RCA 근거):

services/voxel/lambda/captured_area.py:47-55python
# Wait for Athena query to finish
while True:
  response = athena.get_query_execution(QueryExecutionId=query_execution_id)
  if response['QueryExecution']['Status']['State'] in ('SUCCEEDED', 'FAILED', 'CANCELLED'):
    break

기대 동작: facility별로 voxel service가 정상 200을 반환해 captured_sizeFootprintHistory가 갱신되어야 한다. 실제 동작: Lambda가 Athena API를 sleep 없이 폴링해 rate limit을 유발 → 500/503 → 상위 rescue에서 false 반환, migration이 실패 처리.

Failure point (본 cluster 관점): app/models/concerns/voxel_module.rb:22 (에러 로그 기록 위치).

Log Evidence#

Datadog query (본 cluster wrapper 에러 전체 재현):

text
service:cupixvista-api-migration-worker status:error @environment:production "failed to calculate captured size"

결과 8건 (2026-07-15 21:33~21:44 KST):

json
{
  "timestamp": "2026-07-15 21:33:59 KST",
  "status": "error",
  "message": "failed to calculate captured size for Facility ID: 594, error: failed to get captured area - error: 503 Service Unavailable",
  "class": "Facility",
  "function": "calculate_captured_size"
}
json
{
  "timestamp": "2026-07-15 21:36:01 KST",
  "message": "failed to calculate captured size for Facility ID: 597, error: failed to get captured area - error: 500 Internal Server Error"
}
json
{
  "timestamp": "2026-07-15 21:38:03 KST",
  "message": "failed to calculate captured size for Facility ID: 599, error: failed to get captured area - error: 500 Internal Server Error"
}
json
{
  "timestamp": "2026-07-15 21:39:23 KST",
  "message": "failed to calculate captured size for Facility ID: 604, error: failed to get captured area - error: 500 Internal Server Error"
}
json
{
  "timestamp": "2026-07-15 21:41:37 KST",
  "message": "failed to calculate captured size for Facility ID: 607, error: failed to get captured area - error: 503 Service Unavailable"
}
json
{
  "timestamp": "2026-07-15 21:42:27 KST",
  "message": "failed to calculate captured size for Facility ID: 609, error: failed to get captured area - error: 500 Internal Server Error"
}
json
{
  "timestamp": "2026-07-15 21:42:27 KST",
  "message": "failed to calculate captured size for Facility ID: 610, error: failed to get captured area - error: 500 Internal Server Error"
}
json
{
  "timestamp": "2026-07-15 21:44:41 KST",
  "message": "failed to calculate captured size for Facility ID: 612, error: failed to get captured area - error: 503 Service Unavailable"
}

Downstream Lambda 응답 (sibling 3a8d30cc RCA 근거, 동일 시간대):

json
{
  "timestamp": "2026-07-15 21:42:20~28 KST",
  "status": "info",
  "message": "Voxel::CapturedArea | handler_response - status_code: 500, body: \"Failed to calculate captured area - An error occurred (ThrottlingException) when calling the GetQueryExecution operation (reached max retries: 3): Rate exceeded\""
}

이 5xx가 상위 Ruby 서비스에서 RestClient::Exception으로 잡혀 SYS20000으로 재발생하고, Facility#calculate_captured_size rescue에서 본 cluster의 로그가 기록됨.

Hypotheses Considered#

# Hypothesis Evidence for Evidence against Verdict
H1 Downstream voxel Lambda Athena GetQueryExecution throttling (sibling 3a8d30cc RCA에서 확정된 근본 원인이 상위로 전파됨) Datadog에서 본 cluster 8건 모두 failed to get captured area - error: 5xx로 downstream 응답을 그대로 인용. 동일 시간대 Lambda 로그에 ThrottlingException ... GetQueryExecution ... Rate exceeded 다수. Lambda 코드 captured_area.py:47-55에 sleep 없는 while 루프. Confirmed
H2 Rails 워커 프로세스 자체의 버그 (nil, timeout, DB 문제) 로그 메시지 형태가 downstream HTTP 응답을 그대로 담고 있음. rescue 문자열이 error: #{e.message} 형태로 RestClient::Exception에서 유래. Ruby 측 예외라면 다른 stack trace/메시지가 관찰됐을 것. Rejected
H3 특정 facility 데이터 이상 (594, 597 등) 특정 ID만 실패 8건 facility ID가 594~612로 연속되며 동일 시간대 마이그레이션 배치 내에서 실패. sibling 로그에는 이후 동일 facility_key가 200으로 정상 처리됨. facility 특성이 아닌 timing 원인. Rejected
H4 Downstream voxel-service 배포/장애 (전면 outage) 503 응답 존재 동시간대 다른 요청은 성공하고 21:49 KST 자연 회복. 배포 이벤트 로그 미확인 → uncertain Rejected (partial)

Fix Recommendation#

즉시 조치 (Critical)#

  • Downstream Lambda services/voxel/lambda/captured_area.py:47-55 polling 루프에 sleep 도입 (예: time.sleep(0.5) 또는 exponential backoff 0.2s → 2s). 본 cluster는 downstream 수정 없이는 재발한다. 자세한 방향은 sibling RCA 3a8d30cc-330a-4d70-8ac8-5ce4883bc982 참조.
  • CUPIXVISTA 마이그레이션 워커 측: Facility#calculate_captured_size가 예외를 삼키고 false를 반환하는 특성 상 (voxel_module.rb:19-27) migration orchestrator가 반환값을 확인해 실패 facility를 재시도 큐로 넣는지 확인 필요. 반환값을 무시하고 있다면 migration 로그에 실패 facility ID 집계와 재시도 훅을 추가.

단기 개선 (1주 이내)#

  • Cupix::VoxelService.captured_area! 클라이언트에 503/500 ThrottlingException 감지 시 backoff retry를 추가하여 상위 워커가 downstream 일시 장애를 흡수하도록 개선 (app/services/cupix/voxel_service.rb:88-99).
  • CUPIXVISTA 마이그레이션 배치의 병렬도(동시 in-flight facility 수) rate limit. 계정 단위 Athena 100 TPS를 하나의 배치가 소모하지 않도록 semaphore 또는 큐 rate limit.

장기 개선 (재발 방지)#

  • Voxel service 호출 패턴을 synchronous HTTP → async job (SQS + polling endpoint or EventBridge)으로 전환 검토. 현재 구조는 마이그레이션 워커가 Lambda + Athena를 synchronous하게 기다리므로 downstream throttling이 즉시 상위 실패로 전파된다.
  • Lambda 내 Athena polling을 Step Functions Athena native integration으로 대체하거나 별도 Athena workgroup으로 격리해 CUPIXVISTA 마이그레이션 batch가 다른 tenant의 정상 트래픽에 영향을 주지 않도록 분리.

Monitoring#

CUPIXVISTA 마이그레이션 워커의 wrapper 에러 (본 cluster fingerprint):

text
service:cupixvista-api-migration-worker status:error "failed to calculate captured size"

CUPIXVISTA 마이그레이션 워커 전체 에러:

text
service:cupixvista-api-migration-worker status:error

Downstream Lambda의 Athena throttling (proof-of-fix에 가장 직접적):

text
"Voxel::CapturedArea" "ThrottlingException" "GetQueryExecution"

Downstream Lambda의 500 응답률:

text
"Voxel::CapturedArea | handler_response - status_code: 500"

Risk Assessment#

  • Risk level: medium — 데이터 손상은 없으나 CUPIXVISTA migration 대상 facility가 stale captured_size로 남을 수 있음. 재실행 없이는 자동 보정되지 않을 가능성이 높다 (rescue가 false만 반환하고 예외를 상위로 전달하지 않음).
  • 예상 복잡도: standard — 실제 코드 수정은 downstream Lambda에 sleep 도입(trivial) + Ruby 워커의 반환값 처리/재시도 정책 확인 + Athena workgroup 분리 검토. 본 cluster 단독으로는 sibling 3a8d30cc fix로 대부분 해소되므로 별도 액션은 migration 재실행/재시도 로직 정비 위주.