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 시나리오 wrapper3a8d30cc-330a-4d70-8ac8-5ce4883bc982— downstream 503 시나리오 (하위Cupix::VoxelService.captured_area!로그)
Timeline#
- 2026-07-15 21:33 KST — Facility 594: 503 (cluster first_seen)
- 2026-07-15 21:36 KST — Facility 597: 500
- 2026-07-15 21:38 KST — Facility 599: 500
- 2026-07-15 21:39 KST — Facility 604: 500
- 2026-07-15 21:41 KST — Facility 607: 503
- 2026-07-15 21:42 KST — Facility 609, 610: 500 (동시)
- 2026-07-15 21:44 KST — Facility 612: 503 (cluster last_seen)
- 2026-07-15 21:49 KST — voxel Lambda 정상 응답 회복 (sibling RCA 로그 근거)
Error Log#
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의 로그 발생 지점):
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!로 위임:
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에서 코드/로그로 확인됨):
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 근거):
# 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_size와 FootprintHistory가 갱신되어야 한다.
실제 동작: 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 에러 전체 재현):
service:cupixvista-api-migration-worker status:error @environment:production "failed to calculate captured size"
결과 8건 (2026-07-15 21:33~21:44 KST):
{
"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"
}
{
"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"
}
{
"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"
}
{
"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"
}
{
"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"
}
{
"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"
}
{
"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"
}
{
"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 근거, 동일 시간대):
{
"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-55polling 루프에 sleep 도입 (예:time.sleep(0.5)또는 exponential backoff 0.2s → 2s). 본 cluster는 downstream 수정 없이는 재발한다. 자세한 방향은 sibling RCA3a8d30cc-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):
service:cupixvista-api-migration-worker status:error "failed to calculate captured size"
CUPIXVISTA 마이그레이션 워커 전체 에러:
service:cupixvista-api-migration-worker status:error
Downstream Lambda의 Athena throttling (proof-of-fix에 가장 직접적):
"Voxel::CapturedArea" "ThrottlingException" "GetQueryExecution"
Downstream Lambda의 500 응답률:
"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
3a8d30ccfix로 대부분 해소되므로 별도 액션은 migration 재실행/재시도 로직 정비 위주.