VoxelService Lambda returns HTTP 500 on Athena failures
RCA: Cupix::Errors::System: failed to merge voxels - error: 500 Internal Server Error
Overview#
What Happened#
GET /api/v1/levels/{id}/voxels (Api::V1::LevelsController#merged_voxels) 요청이 downstream voxel service ($CUPIX_VOXEL_SERVICE_URL/merge, data-pipeline-functions Lambda) 를 호출한다. Lambda 가 500 을 반환하면 tesla 의 Cupix::VoxelService.merge_voxel 이 RestClient::Exception 을 잡아 Cupix::Errors::System (code SYS20000) 로 재발생시키고, 사용자에게는 500 응답이 나간다. 2026-07-14 부터 2026-08-01 까지 여러 tenant 와 level 에 걸쳐 374건 발생했다.
Quick Facts#
| Field | Value |
|---|---|
| exception.class | Cupix::Errors::System |
| exception.message | failed to merge voxels - error: 500 Internal Server Error |
| exception.code | SYS20000 |
| top_frame | app/services/cupix/voxel_service.rb:68 |
| downstream | voxel service PUT $CUPIX_VOXEL_SERVICE_URL/merge (data-pipeline-functions merge_voxel.py) |
| runtime | Rails (tesla), Ruby |
| deploy | production-us-west-2-20260731T0510Z0-5d9c7658-cupixworks |
| env | production, us-west-2 |
Affected Teams#
@http.status_code:500 인 merged_voxels 요청 로그 (최근 14일, 100건 샘플) 의 team.domain 분포다. 특정 tenant 하나가 아니라 여러 tenant 에 걸쳐 발생한다.
| Team / Domain | Error Count (sample) | Impact |
|---|---|---|
| southlandind | 71 | 특정 level (66844, 66884) 에서 merge 반복 실패 |
| whitingturner | 6 | merged voxel 다운로드 실패 |
| realitysa | 4 | merged voxel 다운로드 실패 |
| cmdintl | 4 | merged voxel 다운로드 실패 |
| clark-vdc | 3 | merged voxel 다운로드 실패 |
| 기타 (siteline, algtest 등) | 나머지 | merged voxel 다운로드 실패 |
Timeline#
- 2026-07-14 17:35 KST — 클러스터 최초 발생 (first_seen).
- 2026-07-23 14:43 KST —
southlandindlevel 66844/66884 에서 대량 발생 (수초 내 수십 건, 클라이언트 재시도 폭주 추정). - 2026-08-01 09:40 KST — 최근 발생 (last_seen),
realitysalevel 55863, 요청 duration 약 22.6초.
Error Log#
failed to merge voxels - error: 500 Internal Server Error
Impact#
- Service:
cupixworks-api - 발생 횟수: 374
- 최초 발생: 2026-07-14 17:35 KST
- 최근 발생: 2026-08-01 09:40 KST
Root Cause Summary#
Root cause 는 downstream voxel service Lambda (merge_voxel.py) 가 500 을 반환하는 것이다. Lambda 는 Athena query 가 FAILED/CANCELLED 로 끝나거나 (merge_voxel.py:136) 처리 중 예외가 발생하면 (merge_voxel.py:145) 500 을 돌려준다. tesla 의 Cupix::VoxelService.merge_voxel 은 이 500 을 RestClient::Exception 으로 받아 Cupix::Errors::System (SYS20000) 로 재발생시킨다 (voxel_service.rb:65-68). tesla 의 Cupix::HttpClient 는 재시도 대상 상태 코드를 [429, 502, 503, 504] 로만 정의하므로 (http_client.rb:8) 500 은 재시도 없이 즉시 전파된다. Representative Error 와 최근 (last_seen 부근) 로그 메시지가 동일하여 stale 하지 않다.
Technical Analysis#
Code Path#
- Entry point:
app/controllers/concerns/voxels_controller.rb:16(merged_voxels) - 컨트롤러가 downstream 호출로 redirect 하는 서비스 진입:
app/services/cupix/voxel_service.rb:48(merge_voxel) - Downstream 호출 및 500 → 예외 재발생 지점 (failure point):
app/services/cupix/voxel_service.rb:60-68 - Downstream 500 발생 원인:
data-pipeline-functions/services/voxel/lambda/merge_voxel.py:136,:145
컨트롤러는 merge 파라미터를 만들고 downstream 결과 signed URL 로 redirect 한다.
def merged_voxels
raw_access_token = request.headers['X-CUPIX-AUTH'] || params['x-cupix-auth']
merge_voxel_params = repository_instance.create_merge_voxel_params!(params)
if merge_voxel_params[:capture_ids].blank? && merge_voxel_params[:pointcloud_ids].blank?
render body: 'x,y,z,w,voxel_size', status: :ok
else
redirect_to Cupix::VoxelService.merge_voxel(merge_voxel_params.merge('x-cupix-auth' => raw_access_token)), allow_other_host: true
end
end
서비스는 downstream 에 PUT /merge 를 보내고, RestClient::Exception 을 잡아 SYS20000 으로 재발생시킨다. downstream 이 200 이 아닌 500 을 주면 여기서 예외가 난다.
begin
response = Cupix::HttpClient.put("#{$CUPIX_VOXEL_SERVICE_URL}/merge", body, headers)
body = JSON.parse(response.body)
body['signed_url']
rescue RestClient::Exception => e
Cupix::Logger.error("failed to merge voxels - error: #{e.message}", class: self.name, function: __method__, model_id: params[:model_id], model_type: params[:model_type])
raise Cupix::Errors::System.new(code: 'SYS20000', reason: "failed to merge voxels - error: #{e.message}")
Cupix::HttpClient 의 재시도 대상은 [429, 502, 503, 504] 뿐이라 500 은 재시도되지 않고 즉시 전파된다.
RETRIABLE_STATUS_CODES = [429, 502, 503, 504].freeze
MAX_RETRIES = 3
Downstream Lambda 는 Athena query 가 성공하지 않으면 500 을 반환하고, except Exception 에서도 500 을 반환한다. 500 응답 본문은 Athena query failed - {reason} 또는 Failed to merge voxel - {error} 형태다.
else:
return handler_response(500, f'Athena query failed - {response["QueryExecution"]["Status"]["StateChangeReason"]}')
except KeyError as e:
return handler_response(400, {
'result': {
'code': 'ARG10000',
'message': 'Missing required attribute in the request body:' + str(e)
}
})
except Exception as e:
return handler_response(500, f'Failed to merge voxel - {str(e)}')
기대 동작은 downstream 이 merge 결과 signed URL 을 200 으로 반환하는 것이다. 실제로는 Athena query 실패 또는 예외로 500 을 반환하고, tesla 는 이를 재시도 없이 사용자 에러로 전파한다. tesla 로그의 e.message 가 500 Internal Server Error 로만 남아 (RestClient 가 status line 만 표기) Athena 실패 사유가 tesla 측 로그에는 드러나지 않는다.
Log Evidence#
Datadog 쿼리 (representative 메시지, last_seen 부근):
service:cupixworks-api "merge voxels"
Time range: 2026-07-31T00:00:00Z ~ 2026-08-01T06:00:00Z. 결과 (요약):
{
"timestamp": "2026-08-01 09:40:24",
"status": "error",
"message": "failed to merge voxels - error: 500 Internal Server Error",
"class": "Cupix::VoxelService",
"function": "merge_voxel"
}
동일 request 의 request 로그 (500, duration 약 22.6초):
{
"message": "[500] GET /api/v1/levels/55863/voxels (Api::V1::LevelsController#merged_voxels)",
"status": "info",
"http": { "status_code": 500, "method": "GET", "url_details": { "path": "/api/v1/levels/55863/voxels" } },
"error": {
"reason": "failed to merge voxels - error: 500 Internal Server Error",
"code": "SYS20000",
"class": "Cupix::Errors::System"
},
"duration": 22637.17,
"team": { "domain": "realitysa", "id": 1150 },
"environment": "production"
}
분포 확인 쿼리 (최근 14일, 100건 샘플):
service:cupixworks-api "merged_voxels" status:info @http.status_code:500
team.domain 별 집계 결과: southlandind 71, whitingturner 6, realitysa 4, cmdintl 4, clark-vdc 3, 기타. path 별 집계: /api/v1/levels/66844/voxels 37, /api/v1/levels/66884/voxels 18, /api/v1/levels/66881/voxels 9, /api/v1/levels/55863/voxels 4 등 다수 level. 2026-07-23 05:43~05:44 (UTC) 구간에 southlandind level 66844/66884 로 수초 내 수십 건이 몰려 있어 클라이언트 재시도 폭주로 보인다.
downstream Lambda 로그는 CloudWatch 에 있고 Datadog 로 수집되지 않아 (tesla RestClient 는 status line 만 로깅), Athena 실패의 구체 사유는 tesla 로그만으로는 확인 불가하다 (uncertain -- CloudWatch 확인 필요).
Hypotheses Considered#
| # | Hypothesis | Evidence for | Evidence against | Verdict |
|---|---|---|---|---|
| H1 | downstream voxel Lambda 가 500 을 반환하고 tesla 가 재시도 없이 전파 | merge_voxel.py:136/:145 가 500 반환; http_client.rb:8 재시도 대상에 500 없음; tesla error 로그 class: Cupix::VoxelService function: merge_voxel |
없음 | Confirmed |
| H2 | representative error 가 stale 하고 실제 최근 메시지가 다름 | Error Tracking 이 변형을 묶는 일반적 특성 | last_seen (2026-08-01 09:40 KST) 로그 메시지가 representative 와 동일 (failed to merge voxels - error: 500 Internal Server Error) |
Rejected |
| H3 | 단일 facility/level 데이터 문제로 인한 국소 이슈 | 07-23 버스트가 southlandind level 66844/66884 에 집중 |
14일 샘플에서 southlandind/whitingturner/realitysa/cmdintl/clark-vdc 등 다수 tenant, 다수 level 에 분산 발생 | Rejected |
| H4 | 일시적 외부 의존성 outage | 500 이 downstream 에서 옴 | status-board svc:cupixworks-api active incident 없음; 500 은 [429,502,503,504] 재시도 대상 아니며 18일간 지속 재발 |
Rejected |
Fix Recommendation#
즉시 조치 (Critical)#
data-pipeline-functions/services/voxel/lambda/merge_voxel.py:109-112의 Athena pollingwhile True루프에 timeout 과 sleep 을 추가한다. 현재 상태 폴링이 무한 루프이고 sleep 도 없어 API Gateway 통합 timeout (약 29초) 에 걸려 500 을 유발할 수 있다. 최근 요청 duration 약 22.6초가 이 timeout 근접 가능성을 시사한다 (uncertain -- CloudWatch/API Gateway 로그로 timeout 여부 확정 필요).- downstream 500 원인 진단을 위해 CloudWatch 에서 해당 Lambda 로그 (
Lambda::MergeVoxel | Athena query id ... state: FAILED,Failed to merge voxel - ...) 를 확인해 Athena 실패 사유를 특정한다.
단기 개선 (1주 이내)#
- tesla
Cupix::VoxelService.merge_voxel(voxel_service.rb:65-68) 에서 downstream 응답 body 를 로그에 포함하도록 개선한다. 현재e.message가500 Internal Server Error만 남아 근본 원인 추적이 불가능하다.RestClient::Exception의e.response&.body를 error 로그에 함께 기록하면 Athena 실패 사유가 tesla 측에서도 보인다. - Lambda 500 응답을 원인별로 분리한다. Athena query 실패 (데이터/쿼리 문제, 재시도 무의미) 와 일시적 예외를 구분해, 후자는 5xx (예: 503) 로 반환하면 tesla
HttpClient재시도 (RETRIABLE_STATUS_CODES) 가 흡수할 수 있다.
장기 개선 (재발 방지)#
- merged voxels 조회를 동기 HTTP redirect 대신 비동기 처리 (job + polling/notification) 로 전환하는 것을 검토한다. Athena query 는 수초~수십초가 걸릴 수 있어 API Gateway/Lambda 동기 timeout 경계에 취약하다.
- 클라이언트 재시도 정책을 검토한다. 07-23 southlandind 버스트처럼 실패한 level 에 대해 수초 내 수십 회 재시도가 발생하면 downstream 부하와 로그 노이즈가 증폭된다.
Monitoring#
merged voxels 5xx 발생 추이 (log search):
service:cupixworks-api status:error "failed to merge voxels"
merged voxels endpoint 의 500 응답 추이 (log search):
service:cupixworks-api @http.status_code:500 "merged_voxels"
Risk Assessment#
- Risk level: medium
- 예상 복잡도: standard
Noise Verdict#
bug — downstream voxel Lambda 의 Athena query 실패/무한 polling 으로 500 이 재시도 없이 전파되며 18일간 다수 tenant·level 에 걸쳐 374건 재발하는 실제 결함이다.