ES /docs

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#

  1. 2025-09-25 06:14 KST — 최초 발생 (first_seen)
  2. 2026-07-22 ~ 2026-08-02 — 최근 14일 창에서 약 11건의 error 로그 반복 확인
  3. 2026-08-02 09:42 KST — 최근 발생 (last_seen), capture 742991 에서 동일 메시지
  4. 2026-08-04 — RCA 수행

Error Log#

Datadog Logs

text
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_cacherescue StandardError 로 오류를 삼켜 warn 만 남기는 것과 대조적으로, add_partition 만 hard fail 하도록 되어 있는 것이 근본 원인이다.

Technical Analysis#

Code Path#

  • Entry point: PUT /api/v1/captures/{id}/check_voxels_uploadingapp/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 은 예외를 그대로 전파한다.

app/models/concerns/voxel_module/reality_capture.rb:55-66ruby
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 로 변환한다.

app/services/cupix/voxel_service.rb:119-133ruby
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 을 재시도 대상에서 제외한다.

lib/cupix/http_client.rb:8-9,56-68ruby
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 을 반환한다.

data-pipeline-functions/services/voxel/lambda/add_partition.py:60-69python
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 쿼리 (재현 가능):

text
service:cupixworks-api "failed to add partition"
text
service:cupixworks-api @function:add_partition status:error

최근 발생(last_seen 2026-08-02 09:42 KST)의 실제 로그 — Representative Error 와 동일한 메시지로, 클러스터의 대표 샘플이 stale 하지 않음을 확인:

json
{
  "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"
  }
}
json
{
  "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_cacherescue 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:58add_partitionremove_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 실패 추이:
text
service:cupixworks-api @function:add_partition status:error
  • check_voxels_uploading 500 응답 추이:
text
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 되지 않아 코드 수정이 필요하다.