ES /docs

BIM360 refresh_token failed: - error: 500 Internal Server Error

RCA: BIM360 refresh_token failed: - error: 500 Internal Server Error

Error Log#

Datadog Logs

text
BIM360 refresh_token failed:  - error: 500 Internal Server Error

Impact#

  • Service: cupixworks-worker
  • 발생 횟수: 1
  • 최초 발생: 2026-04-13T16:07:22.677Z
  • 최근 발생: 2026-04-13T16:07:22.677Z

Root Cause Summary#

BIM360(Autodesk) OAuth token refresh 크론 작업(Cupix::Cron::Integration.renew_before_expiration)이 4시간마다 실행되면서 만료 예정인 integration의 refresh token을 갱신하는 과정에서, eu-central-1 리전의 integration ID 239에 대해 Autodesk Forge OAuth API가 HTTP 500 Internal Server Error를 반환했습니다. 이는 Autodesk 서버 측의 일시적 장애로, 해당 시점에 refresh token은 아직 유효한 상태였습니다(refresh_token_expired_at: 2026-04-14 12:07:17 UTC, 에러 발생 약 20시간 전). 현재 코드에는 일시적 외부 API 실패에 대한 재시도 로직이 없어, 단 한 번의 500 응답으로 integration이 failed 상태로 전환되었습니다.

Technical Analysis#

Code Path#

  • Entry point: config/schedule.rb:94 — 4시간마다(7 */4 * * *) Cupix::Cron::Integration.renew_before_expiration 실행
ruby
# config/schedule.rb:91-94
every '7 */4 * * *' do # 00:07,04:07,08:07 ...
  runner 'Cupix::Cron::Integration.renew_before_expiration'
end
  • Cron handler: lib/cupix/cron/integration.rb:3-16 — 만료 예정인 integration을 조회하여 순차적으로 refresh
ruby
# lib/cupix/cron/integration.rb:3-16
def self.renew_before_expiration
  candidates = Integration.refresh_token_due_to_expire
  return nil if candidates.blank?

  candidates.each do |model|
    integration_repository = IntegrationRepository.new(model)
    integration_repository.refresh_token
  rescue StandardError => e
    Cupix::Logger.error("[Cupix::Cron::Integration] failed to renew integration: #{e.message}")
    next  # 실패해도 다른 integration은 계속 처리
  end
end
  • Scope 조건: app/models/concerns/statable/integration.rb:12refresh_token_expired_at이 1일 이내인 비-실패 상태의 integration을 대상으로 함
ruby
# app/models/concerns/statable/integration.rb:12
scope :refresh_token_due_to_expire, -> {
  not_failed.where.not(refresh_token_expired_at: nil)
            .where('refresh_token_expired_at < ?', 1.days.since)
}
  • Repository 실행: app/repositories/integration_repository.rb:103-142 — provider별 operation class를 선택하고 token refresh를 실행
ruby
# app/repositories/integration_repository.rb:123-142
begin
  unless check_refresh_request
    raise Cupix::Errors::Parameter.new(code: 'ARG10060', reason: 'Refresh access token request is too many')
  end

  token = operation_class.refresh_token(@model.refresh_token, @model.region)
  uncheck_refresh_request
rescue StandardError => e
  uncheck_refresh_request

  raise e if e.code == 'ARG10060'

  @model.refresh_token_failed_at = DateTime.now
  @model.refresh_token_expired_at = nil   # 만료 시각 초기화
  @model.refresh_token_response_body = token
  @model.failed_state!                    # failed 상태로 전환 — 복구 불가

  Cupix::Logger.error("[Integration] failed to refresh token for #{@model.provider} integration(#{@model.id}) - state: #{@model.state}, error_message: #{e.message}")

  raise e
end
  • Failure point: app/operations/bim360_operation.rb:60-70 — RestClient가 Autodesk API에 POST 요청 시 500 응답을 받아 예외 발생
ruby
# app/operations/bim360_operation.rb:60-70
begin
  url = "#{$OAUTH[:autodesk_forge][:site]}#{$OAUTH[:autodesk_forge][:refresh_url]}"
  response = RestClient.post(url, data, header)
rescue RestClient::Exception => e
  response = JSON.parse(e.response)

  Cupix::Logger.error("BIM360 refresh_token failed: #{response['developerMessage']} - error: #{e.message}")
  raise Cupix::Errors::Parameter.new(
    code: 'ARG10000',
    reason: "BIM360 Authentication failed: #{response['developerMessage']}"
  )
end

기대 동작: Autodesk API가 새 access_token과 refresh_token을 JSON으로 반환하고, integration의 토큰이 갱신됨.

실제 동작: Autodesk API가 HTTP 500을 반환 → RestClient::Exception 발생 → Cupix::Errors::Parameter raise → IntegrationRepository에서 catch하여 integration 239를 failed 상태로 전환 → refresh_token_expired_at을 nil로 설정. 이후 크론 작업에서는 not_failed scope으로 인해 이 integration이 자동 갱신 대상에서 제외됨.

Log Evidence#

사용한 Datadog 쿼리:

text
service:cupixworks-worker status:error "BIM360 refresh_token failed"
text
service:cupixworks-worker status:error "BIM360"
text
service:cupixworks-worker "BIM360"

에러 로그 타임라인 (eu-central-1, 2026-04-13T16:07:22.677Z):

1단계 — Autodesk API 500 응답:

text
BIM360 refresh_token failed:  - error: 500 Internal Server Error
Host: ip-10-1-80-125.eu-central-1.compute.internal, PID: 3049253

2단계 — Integration 상태 전환:

text
[Integration] failed to refresh token for bim360 integration(239) - state: failed, error_message: BIM360 Authentication failed:

3단계 — Cron 레벨 에러 보고:

text
[Cupix::Cron::Integration] failed to renew integration: BIM360 Authentication failed:

Integration 239의 refresh 전 상태 (info 로그):

text
[Integration] refresh token for bim360 integration(239) - state: inactive, expired_at: 2026-03-31 13:21:16 UTC, refresh_token_expired_at: 2026-04-14 12:07:17 UTC
  • refresh_token_expired_at2026-04-14 12:07:17 UTC로 에러 시점에서 약 20시간 남아 있어, refresh token 자체의 만료가 아닌 Autodesk 서버 측 일시적 오류임을 확인.
  • state: inactive이며 expired_at: 2026-03-31 — access token은 이미 만료되었으나 refresh token은 유효한 상태.

대조: 동일 시간대 us-west-2 리전에서는 48개 이상의 BIM360 integration이 정상 갱신됨 — 문제가 Autodesk EU 리전 서버에 국한된 일시적 장애임을 시사.

Fix Recommendation#

즉시 조치 (Critical)#

  • app/operations/bim360_operation.rb:60-62: RestClient.post 호출에 재시도 로직 추가. HTTP 500, 502, 503, 504 등 서버 오류에 대해 지수 백오프(exponential backoff)로 최대 3회 재시도하도록 변경. 일시적 Autodesk 서버 장애로 인해 integration이 영구 failed 상태로 전환되는 것을 방지.

단기 개선 (1주 이내)#

  • app/repositories/integration_repository.rb:135-138: 일시적 외부 API 오류(5xx)와 영구적 인증 오류(401, 403)를 구분하여, 5xx 에러의 경우 즉시 failed 상태로 전환하지 않고 refresh_token_failed_at만 기록한 후 다음 크론 사이클에서 재시도할 수 있도록 변경. 현재는 단 한 번의 500 응답으로도 failed_state!가 호출되어 복구가 불가능함.

  • app/models/concerns/statable/integration.rb:12: refresh_token_due_to_expire scope에 최근 실패한 integration도 일정 횟수까지 재시도 대상에 포함하는 로직 고려. 현재 not_failed 조건으로 인해 한 번 실패하면 자동 갱신 대상에서 완전히 제외됨.

장기 개선 (재발 방지)#

  • Integration의 failed 상태에서 자동 복구 메커니즘 도입. 예: 연속 실패 횟수(retry_count)를 추적하여, 일정 횟수(예: 5회) 이상 연속 실패 시에만 failed 상태로 전환하고 관리자 알림 발송.
  • Autodesk API 응답 시간 및 에러율에 대한 외부 서비스 모니터링 추가.

Monitoring#

  • BIM360 token refresh 실패를 감지하는 Datadog 모니터 추가:
text
service:cupixworks-worker "BIM360 refresh_token failed" status:error
  • Integration failed 상태 전환 빈도 추적:
text
service:cupixworks-worker "[Integration] failed to refresh token" status:error
  • 리전별 BIM360 refresh 성공/실패 비율 대시보드 구성을 통해 특정 리전의 Autodesk API 장애를 조기 감지.

Risk Assessment#

  • Risk level: low
  • 예상 복잡도: standard
  • 단일 integration(ID 239)에만 영향. eu-central-1 리전의 Autodesk 서버 일시적 장애로 판단되며, 동일 시간대 us-west-2의 모든 integration은 정상 갱신됨. 그러나 재시도 로직 부재로 인해 일시적 외부 오류에도 integration이 영구 실패 상태로 전환되는 구조적 취약점이 존재함.