ES /docs

Cupix::Errors::BadGateway: {"result"=>{"code"=>"BG10004", "type"=>"Cupix::Errors::BadGateway", "reason"=>"Failed to fetc

RCA: Cupix::Errors::BadGateway BG10004 — Failed to fetch facility info (401 Unauthorized)

Overview#

What Happened#

Facility migration 요청 처리 중 migration-service(cupixworks monorepo)가 tesla API(GET /api/v1/facilities/{id})를 호출해 source/destination facility 정보를 가져오려다 tesla 가 401 Unauthorized 를 반환했다. migration-service 는 이 401 을 Cupix::Errors::BadGateway(code BG10004)로 감싸 502 로 응답하고 error 레벨 로그를 남긴다. 2025-11-28 부터 2026-07-28 까지 약 8개월간 51건이 산발적으로 발생했다.

APM service 필드는 cupixworks-api 로 기록되어 있으나, 에러 메시지("Failed to fetch facility info from tesla server")와 code BG10004 문자열은 tesla 가 아니라 tesla 를 호출하는 migration-service 에서 발생한 것이다.

Quick Facts#

Field Value
exception.class Cupix::Errors::BadGateway
exception.message Unable to retrieve facility info: 401 Unauthorized
top_frame applications/migration-service/app/factories/migration_factory.rb:174-180
runtime Ruby (Rails, cupixworks migration-service)
env production (cross-environment facility migration flow)

Affected Teams#

Team / Domain Error Count Impact
migration-service (facility migration) 51 (8개월 누적) facility migration 요청이 502 로 실패. 만료/무효 토큰으로 재시도한 소수 사용자에 국한

Timeline#

  1. 2025-11-28 14:45 KST — 최초 발생 (first_seen, representative sample)
  2. 2026-07-28 20:08 KST — 최근 발생 (last_seen)
  3. 2026-08-04 — RCA 수행

Error Log#

Datadog Logs

text
{"result"=>{"code"=>"BG10004", "type"=>"Cupix::Errors::BadGateway", "reason"=>"Failed to fetch facility info from tesla server", "message"=>"Unable to retrieve facility info: 401 Unauthorized"}}

Impact#

  • Service: cupixworks-api (APM 태그; 실제 발생 서비스는 migration-service)
  • 발생 횟수: 51
  • 최초 발생: 2025-11-28 14:45 KST
  • 최근 발생: 2026-07-28 20:08 KST

Root Cause Summary#

facility migration 검증 흐름에서 MigrationFactory.set_facility_team_ids 가 caller 로부터 전달받은 from[:auth_token] / to[:auth_token] 로 source/destination tesla endpoint 의 GET /api/v1/facilities/{id} 를 호출한다. 이 토큰이 해당 endpoint(환경/리전/tenant)에 대해 만료되었거나 유효하지 않으면 tesla 가 401 Unauthorized 를 반환한다. fetch_facility_inforescue RestClient::Exception 절은 모든 upstream 실패를 401(client 인증 오류)/500/503(server 오류) 구분 없이 일괄로 Cupix::Errors::BadGateway(BG10004)로 감싸 raise 하고, 이는 server_error_controller.rb 에서 502 응답 + Cupix::Logger.error 로 기록되어 Error Tracking 에 잡힌다. 즉 client-side 인증 실패(401)를 server-side gateway 오류(502 error)로 오분류하는 것이 근본 원인이다.

Technical Analysis#

Code Path#

  • Entry point: applications/migration-service/app/controllers/migrations_controller.rb:48 (MigrationsController#create)
  • caller 가 from / to 페이로드(각 auth_token 포함)를 전달
applications/migration-service/app/controllers/migrations_controller.rb:48-57ruby
  def create
    migration = MigrationFactory.create(
      session: params[:session],
      type: params[:type],
      source: params[:source],
      from: params[:from],
      to: params[:to],
      sender: params[:sender],
      service_name: params[:service_name]
    )
  • facility & non-move migration 일 때만 tesla 조회 진입: migration_factory.rb:28-31
applications/migration-service/app/factories/migration_factory.rb:28-31ruby
      if to[:model_name] == 'facility' && type != 'move'
        set_facility_team_ids(from, to)
        validate_facility_settings_match(from, to) if production_destination?(to)
      end
  • source/destination endpoint 를 각각 조회하며 caller 토큰 사용: migration_factory.rb:43-51
applications/migration-service/app/factories/migration_factory.rb:43-51ruby
    def set_facility_team_ids(from, to)
      from_endpoint = MigrationOperation.generate_tesla_endpoint(from[:environment], from[:region], from[:tenant])
      to_endpoint = MigrationOperation.generate_tesla_endpoint(to[:environment], to[:region], to[:tenant])

      from[:facility_info] = fetch_facility_info(from_endpoint, from[:model_id], from[:auth_token])
      to[:facility_info] = fetch_facility_info(to_endpoint, to[:model_id], to[:auth_token])
  • Failure point: migration_factory.rb:174-180 — tesla 401 을 잡아 무조건 BG10004(502) 로 변환
applications/migration-service/app/factories/migration_factory.rb:168-180ruby
      response = RestClient.get(url, { 'x-cupix-auth': auth_token })
      facility_data = JSON.parse(response.body)
      # ...
    rescue RestClient::Exception => e
      Cupix::Logger.error("Failed to fetch facility info from #{url} - #{e.response.body}", class: self.class.name, function: __method__, error: e)
      raise Cupix::Errors::BadGateway.new(
        code: 'BG10004',
        reason: 'Failed to fetch facility info from tesla server',
        message: "Unable to retrieve facility info: #{e.message}"
      )
  • 변환된 예외는 502 + error 로그로 처리: server_error_controller.rb:7-19
applications/migration-service/app/controllers/concerns/server_error_controller.rb:7-19ruby
    rescue_from Cupix::Errors::BadGateway, with: :sever_502_error

    def sever_502_error(exception)
      raise_error(
        502,
        exception,
        code: 'BG10000',
        type: Cupix::Errors::BadGateway, reason: 'BadGateway',
        message: exception.message
      )
      Cupix::Logger.error("[Migration] Server 502 error - #{exception}", class: self.class.name, function: __method__)
    end

기대 동작 vs 실제 동작: 기대 — 유효한 토큰이라면 facility 정보를 200 으로 받아 migration 진행. 실제 — 토큰이 대상 endpoint 에 유효하지 않으면 tesla 가 401 을 반환하고, migration-service 는 이를 502 BadGateway 로 오분류하여 error 로그를 남긴다. 401(요청자 인증 문제)과 500/503(tesla 서버 문제)이 동일 경로로 뭉개져 알람 노이즈가 된다.

Log Evidence#

Datadog 로그 검색 (retention 14일 내):

text
service:cupixworks-api "Failed to fetch facility info"    → 0 logs
service:migration-service "Failed to fetch facility info" → 0 logs
"Failed to fetch facility info from"                       → 0 logs

Representative sample(Error Tracking first_seen, 2025-11-28)의 원문:

json
{"result":{"code":"BG10004","type":"Cupix::Errors::BadGateway","reason":"Failed to fetch facility info from tesla server","message":"Unable to retrieve facility info: 401 Unauthorized"}}

et: Error Tracking issue 특성상 last_seen(2026-07-28) 이 Datadog 로그 retention(14일) 밖이라 매칭 로그 0건이다. 다만 이 issue 의 message 포맷은 코드에 상수로 고정(Unable to retrieve facility info: #{e.message})되어 있고, e.message401 Unauthorized 인 것은 tesla 401 응답을 그대로 담은 것이므로, representative 와 current occurrence 모두 동일한 401 시나리오임이 코드로 확정된다 — Representative Error 와 현재 발생 메시지 간 불일치는 관찰되지 않는다.

repo 확정 증거 (grep):

text
/repos/cupixworks/applications/migration-service/app/factories/migration_factory.rb:178
  reason: 'Failed to fetch facility info from tesla server'
/repos/cupixworks/applications/migration-service/app/factories/migration_factory.rb:179
  message: "Unable to retrieve facility info: #{e.message}"

Hypotheses Considered#

# Hypothesis Evidence for Evidence against Verdict
H1 caller 가 대상 endpoint 에 유효하지 않은/만료된 auth_token 을 전달 → tesla 401 → migration-service 가 502 BadGateway 로 오분류 migration_factory.rb:47-48 caller 토큰 사용; :174-180 모든 RestClient::Exception 을 BG10004 로 변환; message 에 401 Unauthorized 그대로 노출 Confirmed
H2 tesla 서버 장애(500/503)로 facility 조회 실패 동일 rescue 경로가 500/503 도 BG10004 로 감쌈 representative/observed message 가 401 Unauthorized 로 명시 — 서버 5xx 가 아님 Rejected
H3 외부 의존성 outage 인시던트의 일부 status-board svc:cupixworks-api::unknown, 이 cluster id 포함 active 인시던트 없음 2026-07-30 resolved 인시던트는 다른 cluster id 집합이며 last_seen(07-28)과도 무관 Rejected
H4 발생 서비스가 tesla(cupixworks-api) 자체 APM service 태그 cupixworks-api 에러 문자열/코드가 tesla repo 에 없고 migration-service 에만 존재 (grep) Rejected

Fix Recommendation#

즉시 조치 (Critical)#

  • 조치 불필요 수준의 즉시 코드 변경 없음. 401 은 요청자 토큰 문제이므로 서버 결함이 아니다.

단기 개선 (1주 이내)#

  • migration_factory.rb:174-180rescue RestClient::Exception 을 상태 코드별로 분기: RestClient::Unauthorized(401)/RestClient::Forbidden(403) 같은 client 인증 오류는 Cupix::Logger.warn + 4xx 계열 client 오류(예: Cupix::Errors::Parameter 또는 인증 전용 코드)로 매핑하고, 500/503 등 tesla 서버 오류만 BG10004 502 + error 로 유지. fetch_levels(:190-197), fetch_floorplans(:199~), fetch_access_code/fetch_access_token 등 동일 패턴의 sibling 메서드도 함께 정렬.
  • 목적: 요청자 인증 실패(사용자 조치 대상)와 tesla gateway 장애(운영 알람 대상)를 구분해 Error Tracking 알람 노이즈 제거.

장기 개선 (재발 방지)#

  • migration 시작 전 from/to 토큰의 유효성/스코프를 사전 검증(preflight)하여, cross-environment/region migration 에서 토큰이 대상 endpoint 에 유효한지 먼저 확인 후 사용자에게 명확한 4xx 메시지 반환.

Monitoring#

  • migration-service 의 facility 조회 401 발생 추이(warn 재분류 후):
text
sum:trace.rack.request.errors{service:migration-service,http.status_code:401}.as_count()
  • migration-service 전체 502 응답 추이(tesla gateway 실장애 신호):
text
sum:trace.rack.request.errors{service:migration-service,http.status_code:502}.as_count()

Risk Assessment#

  • Risk level: low
  • 예상 복잡도: standard (rescue 분기 좁힘 + sibling 메서드 정렬 + spec 업데이트)

Noise Verdict#

noise — 401 은 요청자가 전달한 만료/무효 토큰에 의한 client 인증 실패로, tesla 나 migration-service 의 코드 결함이 아니며 8개월간 51건으로 산발적이다(다만 401→502 error 오분류로 인한 알람 노이즈는 rescue 분기 좁힘으로 개선 권장).