Cupix::Errors::System: failed to add partition - error: 500 Internal Server Error
RCA: Cupix::Errors::System: failed to add partition - error: 500 Internal Server Error
Overview#
What Happened#
cupixworks-api(tesla)의 PUT /api/v1/captures/{id}/check_voxels_uploading 요청 처리 중, voxel state 가 done 으로 전이되는 before_transition 콜백에서 Cupix::VoxelService.add_partition 이 downstream voxel Lambda 를 호출한다. Lambda 가 Athena ALTER TABLE ... ADD PARTITION 쿼리 실패로 500 을 반환하면, tesla 는 이를 재시도 없이 hard error(Cupix::Errors::System SYS20000)로 raise 하여 전이 자체가 중단되고 클라이언트에 500 이 반환된다. 2025-09-25 부터 약 10개월간 지속적으로 25회 발생했다.
Quick Facts#
| Field | Value |
|---|---|
| exception.class | Cupix::Errors::System |
| exception.message | failed to add partition - error: 500 Internal Server Error |
| exception.code | SYS20000 |
| top_frame | app/services/cupix/voxel_service.rb:128 |
| endpoint | PUT /api/v1/captures/{id}/check_voxels_uploading |
| downstream | voxel Lambda add_partition.py (data-pipeline-functions) |
| env | production |
Affected Teams#
| Team / Domain | Error Count | Impact |
|---|---|---|
| cupixworks-api (voxel upload) | 25 (10개월 누적) | voxel 업로드 완료 확인 요청이 500 으로 실패, voxel state 가 done 으로 전이되지 못함 |
Timeline#
- 2025-09-25 06:14 KST — 최초 발생 (first_seen)
- 2026-07-22 ~ 2026-08-02 — 최근 14일 창에서 약 11건의 error 로그 반복 확인
- 2026-08-02 09:42 KST — 최근 발생 (last_seen), capture 742991 에서 동일 메시지
- 2026-08-04 — RCA 수행
Error Log#
failed to add partition - error: 500 Internal Server Error
Impact#
- Service:
cupixworks-api - 발생 횟수: 25
- 최초 발생: 2025-09-25 06:14 KST
- 최근 발생: 2026-08-02 09:42 KST
Root Cause Summary#
Voxel 업로드 완료 시 partition 을 Athena 에 등록하는 것은 부수효과(best-effort)에 가깝지만, 현재 코드는 이 실패를 치명적 오류로 취급한다. add_partition 은 voxel state 를 done 으로 바꾸는 before_transition 콜백 안에서 호출되는데, downstream voxel Lambda 가 Athena 쿼리 실패로 500 을 반환하면 Cupix::HttpClient.put 은 이를 재시도하지 않고(RETRIABLE_STATUS_CODES = [429, 502, 503, 504] 에 500 미포함) RestClient::Exception 을 raise 한다. Cupix::VoxelService.add_partition 은 이를 잡아 Cupix::Errors::System 으로 재raise 하며, 이 예외가 before_transition 콜백을 통해 전파되어 state 전이 전체가 중단되고 check_voxels_uploading 요청이 500 으로 실패한다. 같은 콜백의 형제 호출인 remove_cache 는 rescue StandardError 로 오류를 삼켜 warn 만 남기는 것과 대조적으로, add_partition 만 hard fail 하도록 되어 있는 것이 근본 원인이다.
Technical Analysis#
Code Path#
- Entry point:
PUT /api/v1/captures/{id}/check_voxels_uploading→app/controllers/concerns/voxels_controller.rb:33 - Repository:
app/repositories/concerns/voxels_repository.rb:33→@model.check_voxels_uploading - Model:
app/models/concerns/voxel_module/s3.rb:39— voxel object 가 존재하면done_voxel_state로 전이 시도 - State machine 콜백:
app/models/concerns/voxel_module/reality_capture.rb:55 - Downstream 호출:
app/services/cupix/voxel_service.rb:120 - Failure point:
app/services/cupix/voxel_service.rb:128
voxel state 를 done 으로 전이하기 직전, 콜백에서 partition 추가와 캐시 제거를 수행한다. remove_cache 는 실패해도 무시되지만 add_partition 은 예외를 그대로 전파한다.
before_transition to: :done do |model, transition|
if model.respond_to?(:increase_voxel_revision)
model.increase_voxel_revision
Cupix::VoxelService.add_partition(model: model) # 실패 시 예외 전파 → 전이 중단
if model.has_attribute?(:level_id)
Cupix::VoxelService.remove_cache(model: model.level) # 실패해도 warn 만
end
true
end
end
add_partition 은 downstream 500 을 재시도 없이 hard error 로 변환한다.
begin
response = Cupix::HttpClient.put("#{$CUPIX_VOXEL_SERVICE_URL}/add_partition", body.to_json, headers)
body = JSON.parse(response.body)
Cupix::Logger.info(body['message'], class: self.name, function: __method__, model: { id: model.id, class: model.class.name })
rescue RestClient::Exception => e
Cupix::Logger.error("failed to add partition - error: #{e.message}", class: self.name, function: __method__, facility_id: model.facility_id, level_id: model.level_id, record_id: model.record_id)
raise Cupix::Errors::System.new(code: 'SYS20000', reason: "failed to add partition - error: #{e.message}")
Cupix::HttpClient.put 은 500 을 재시도 대상에서 제외한다.
RETRIABLE_STATUS_CODES = [429, 502, 503, 504].freeze
MAX_RETRIES = 3
# ...
def self.put(url, payload, headers = {}, retries: MAX_RETRIES)
attempt = 0
begin
RestClient.put(url, payload, headers)
rescue RestClient::Exception => e
if RETRIABLE_STATUS_CODES.include?(e.http_code) && attempt < retries
attempt += 1
sleep((2**(attempt - 1)) + rand(0.0..0.5))
retry
end
raise
end
end
downstream voxel Lambda 는 Athena partition 쿼리가 SUCCEEDED 가 아니면 500 을 반환한다.
if response['QueryExecution']['Status']['State'] == 'SUCCEEDED':
print("Voxel::CapturedArea | Successfully added partition, Athena query id: " + query_execution_id)
return handler_response(200, { 'message': 'Successfully added partition' })
else:
print("Voxel::CapturedArea | Failed Athena query id: " + query_execution_id + ", reason: " + response['QueryExecution']['Status']['StateChangeReason'])
return handler_response(500, {'message': 'Failed to add partition'})
except KeyError as e:
return handler_response(400, {'message': 'Missing required parameter: ' + str(e)})
except Exception as e:
return handler_response(500, f'Failed to add partition - {str(e)}')
기대 동작 vs 실제 동작: partition 등록은 voxel 데이터 쿼리 최적화를 위한 후속 작업으로, 실패하더라도 voxel 업로드 완료 자체(state=done)는 성공 처리하고 partition 은 재시도/보정하는 것이 바람직하다. 실제로는 partition 실패가 전이를 중단시켜 클라이언트가 500 을 받고, voxel state 가 done 으로 전이되지 않는다.
Log Evidence#
Datadog 쿼리 (재현 가능):
service:cupixworks-api "failed to add partition"
service:cupixworks-api @function:add_partition status:error
최근 발생(last_seen 2026-08-02 09:42 KST)의 실제 로그 — Representative Error 와 동일한 메시지로, 클러스터의 대표 샘플이 stale 하지 않음을 확인:
{
"timestamp": "2026-08-02 09:42:39",
"status": "info",
"message": "[500] PUT /api/v1/captures/742991/check_voxels_uploading (Api::V1::CapturesController#check_voxels_uploading)",
"error": {
"reason": "failed to add partition - error: 500 Internal Server Error",
"code": "SYS20000",
"message": "failed to add partition - error: 500 Internal Server Error",
"class": "Cupix::Errors::System"
}
}
{
"timestamp": "2026-08-02 09:42:39",
"status": "error",
"message": "failed to add partition - error: 500 Internal Server Error",
"class": "Cupix::VoxelService",
"function": "add_partition"
}
동일 메시지가 여러 capture(742991, 746312, 745415, 744595, 743255, 740019 등)에서 반복 관찰되며, 특정 capture 에 국한되지 않는다. tesla 로그에는 RestClient 의 500 Internal Server Error 텍스트만 남고 Athena 의 StateChangeReason 은 downstream Lambda(CloudWatch)에만 기록되어, 구체적 Athena 실패 원인은 tesla 로그에서 확인 불가 — needs verification via CloudWatch.
Hypotheses Considered#
| # | Hypothesis | Evidence for | Evidence against | Verdict |
|---|---|---|---|---|
| H1 | best-effort partition 등록 실패를 hard error 로 취급하여 voxel-done 전이를 중단시키는 error-handling 결함 | reality_capture.rb:55-66 콜백에서 add_partition 예외 전파, 형제 remove_cache 는 rescue StandardError로 무시(voxel_service.rb:140-141); voxel_service.rb:128 hard raise; [500] PUT .../check_voxels_uploading 로그 |
— | Confirmed |
| H2 | 외부 의존성(Athena/AWS) 대규모 outage | — | status-board active: null, 10개월간 저빈도(25회) 산발 발생 — 지속 outage 아님 |
Rejected |
| H3 | 대표 에러(Representative Error)가 stale 하여 실제 최근 메시지와 상이 | — | last_seen(2026-08-02) 로그 메시지가 대표 에러와 정확히 일치 | Rejected |
| H4 | 재시도가 있었으나 소진되어 500 반환 | — | HttpClient::RETRIABLE_STATUS_CODES = [429,502,503,504] 에 500 미포함 → 재시도 자체가 없음 (http_client.rb:8) |
Rejected |
Fix Recommendation#
즉시 조치 (Critical)#
app/models/concerns/voxel_module/reality_capture.rb:58—add_partition을remove_cache와 동일하게 best-effort 로 만들어, partition 등록 실패가 voxel-done 전이를 중단시키지 않도록 한다.add_partition실패 시warn로그만 남기고 전이는 계속 진행하는 방향. voxel 업로드 완료(state=done)는 partition 등록과 독립적으로 성공 처리되어야 한다.- 대안으로
app/services/cupix/voxel_service.rb:125-128자체에서 500(비복구성 downstream 실패)을warn로그 후 삼키도록 조정할 수 있으나, 다른 호출 지점의 계약을 바꿀 수 있으니 콜백 레벨(H1)에서 rescue 하는 것이 안전.
단기 개선 (1주 이내)#
- partition 등록 실패를 놓치지 않도록, 전이는 진행하되 실패한 model 을 재처리(재시도 큐/백필 job)하는 경로를 추가. Athena partition 은 이후 조회 시
ADD IF NOT EXISTS로 멱등 처리되므로 재시도 안전. - downstream Lambda(
add_partition.py:64)의StateChangeReason을 tesla 로그/응답 body 에 전달해 실제 Athena 실패 원인을 tesla 측에서도 관측 가능하게 한다.
장기 개선 (재발 방지)#
- voxel partition 등록을 동기 요청 경로에서 분리하여 비동기 background job 으로 처리, 사용자 대면 요청(
check_voxels_uploading)이 데이터 파이프라인 부수작업에 커플링되지 않도록 한다. HttpClient재시도 정책 재검토 — Athena 500 이 transient 성격이 있다면(쿼리 큐잉/스로틀) 제한된 재시도 대상에 포함할지 downstream 팀과 협의.
Monitoring#
- add_partition 실패 추이:
service:cupixworks-api @function:add_partition status:error
- check_voxels_uploading 500 응답 추이:
service:cupixworks-api "check_voxels_uploading" @error.code:SYS20000
Risk Assessment#
- Risk level: medium — 저빈도이나 발생 시 사용자 대면 500 및 voxel-done 전이 차단.
- 예상 복잡도: standard — 콜백 rescue 추가 + 재처리 경로 설계.
Noise Verdict#
bug — best-effort partition 등록 실패를 hard error 로 취급해 voxel-done 전이를 중단시키고 사용자 요청에 500 을 반환하는 error-handling 결함으로, 형제 호출 remove_cache 와 달리 rescue 되지 않아 코드 수정이 필요하다.