ES /docs

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

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

Error Log#

Datadog Logs

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

Impact#

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

Root Cause Summary#

4시간 간격으로 실행되는 Cupix::Cron::Integration.renew_before_expiration 크론 작업이 eu-central-1 리전의 BIM360 integration(239)에 대해 OAuth refresh token을 갱신하려 했으나, Autodesk Forge OAuth API가 HTTP 500 Internal Server Error를 반환하여 실패했습니다. 해당 integration의 access token은 이미 13일 전(2026-03-31)에 만료된 상태였고, refresh token도 약 20시간 후(2026-04-14 12:07:17 UTC)에 만료 예정이었습니다. Autodesk 측 서버 오류로 인해 토큰 갱신이 불가능했으며, 실패 후 integration 상태가 failed로 전환되어 이후 크론 실행에서도 갱신 대상에서 제외됩니다. 동일 시간대 us-west-2 리전의 다른 BIM360 integration들은 모두 정상적으로 갱신되었으므로, 이 건은 Autodesk API의 일시적 오류에 의한 단건 장애입니다.

Technical Analysis#

Code Path#

  • Entry point: config/schedule.rb:91 — 4시간 간격 크론 스케줄 (every '7 */4 * * *')
  • 크론 실행: lib/cupix/cron/integration.rb:3renew_before_expiration 메서드가 갱신 대상 integration을 조회
ruby
# lib/cupix/cron/integration.rb:3-14
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
  end
end
  • 대상 선정: app/models/concerns/statable/integration.rb:12refresh_token_due_to_expire scope가 not_failed 상태이면서 refresh_token_expired_at이 1일 이내인 integration을 조회
ruby
# app/models/concerns/statable/integration.rb:11-12
scope :not_failed, -> { where(state: %i[active inactive]) }
scope :refresh_token_due_to_expire, -> { not_failed.where.not(refresh_token_expired_at: nil).where('refresh_token_expired_at < ?', 1.days.since) }
  • 토큰 갱신: app/repositories/integration_repository.rb:103-128IntegrationRepository#refresh_token이 provider별 operation 클래스를 선택하여 토큰 갱신 요청
ruby
# app/repositories/integration_repository.rb:106-128
Cupix::Logger.info("[Integration] refresh token for #{@model.provider} integration(#{@model.id}) - state: #{@model.state}, expired_at: #{@model.expired_at}, refresh_token_expired_at: #{@model.refresh_token_expired_at}")
operation_class = case @model.provider
                  when 'bim360'
                    Bim360Operation
                  # ...
                  end

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)
  • Failure point: app/operations/bim360_operation.rb:62-70 — Autodesk OAuth API 호출 시 RestClient::Exception 발생 (HTTP 500), Cupix::Errors::Parameter 예외로 변환
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']}"
  )
  • 실패 후 처리: app/repositories/integration_repository.rb:130-142 — 예외 catch 후 integration 상태를 failed로 전환하고 refresh_token_expired_at을 nil로 설정
ruby
# app/repositories/integration_repository.rb:130-142
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!

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

  raise e

기대 동작: Autodesk Forge OAuth API가 새로운 access_token/refresh_token을 반환하고, integration이 갱신됨. 실제 동작: Autodesk API가 HTTP 500을 반환하여 토큰 갱신 실패. Integration 상태가 failed로 전환되어 이후 크론에서 갱신 대상에서 영구 제외됨.

Log Evidence#

사용한 Datadog 쿼리:

text
service:cupixworks-worker status:error "BIM360" @environment:production
text
service:cupixworks-worker "Cron::Integration" OR "BIM360" OR "renew" @environment:production
text
service:cupixworks-worker status:error "failed to renew integration"

핵심 로그 타임라인 (모두 2026-04-13T16:07:22.677Z, 동일 호스트 ip-10-1-80-125.eu-central-1.compute.internal, PID 3049253):

  1. [INFO] 갱신 시도 시작 — integration(239)의 상태 확인:
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
  1. [ERROR] Autodesk API 호출 실패 — HTTP 500 응답:
text
BIM360 refresh_token failed:  - error: 500 Internal Server Error

developerMessage가 비어 있음 (Autodesk 서버 내부 오류로 상세 메시지 미제공).

  1. [ERROR] Integration 상태 변경failed 상태로 전환:
text
[Integration] failed to refresh token for bim360 integration(239) - state: failed, error_message: BIM360 Authentication failed:
  1. [ERROR] 크론 작업 최종 에러 로그:
text
[Cupix::Cron::Integration] failed to renew integration: BIM360 Authentication failed:

비교: 같은 시간대 us-west-2 리전은 정상 갱신:

text
[Integration] Successfully refreshed token for bim360 integration(4877) - state: active
[Integration] Successfully refreshed token for bim360 integration(4742) - state: active
[Integration] Successfully refreshed token for bim360 integration(5817) - state: active

us-west-2에서는 수십 개의 BIM360 integration이 모두 정상적으로 갱신되었으며, eu-central-1의 integration(239)만 실패했습니다.

최근 7일간 동일 에러 검색 결과: 이 건이 유일한 "failed to renew integration" 에러입니다.

Fix Recommendation#

즉시 조치 (Critical)#

  • integration(239) 수동 복구: 현재 failed 상태로 전환되어 크론 갱신 대상에서 제외된 상태입니다. 사용자가 BIM360 연동을 다시 설정(재인증)하거나, DB에서 수동으로 상태를 inactive로 복원하고 refresh token이 아직 유효한 경우 다음 크론 실행에서 갱신을 재시도할 수 있습니다. 다만 refresh_token_expired_at이 2026-04-14 12:07:17 UTC이므로 이미 만료되었을 가능성이 높아 재인증이 필요합니다.

단기 개선 (1주 이내)#

  • 재시도 로직 추가 (app/repositories/integration_repository.rb:130): 외부 API의 5xx 오류 시 즉시 failed 상태로 전환하지 않고, 재시도 카운터를 두어 N회 연속 실패 후에만 failed로 전환하는 방식이 필요합니다. 현재는 Autodesk API의 일시적 500 에러 한 번으로 integration이 영구적으로 비활성화됩니다.
  • 에러 분류 개선 (app/operations/bim360_operation.rb:63-70): 4xx 에러(인증 문제)와 5xx 에러(서버 오류)를 구분하여, 5xx의 경우 일시적 오류로 처리하고 4xx의 경우에만 failed 상태로 전환하는 로직이 필요합니다.

장기 개선 (재발 방지)#

  • 상태 복구 메커니즘: failed 상태의 integration에 대해 일정 기간 후 자동으로 재시도하는 별도 크론 작업 또는 관리자 알림을 추가하여, 일시적 외부 장애로 인한 영구 비활성화를 방지해야 합니다.
  • Alerting: Integration 갱신 실패 시 관련 팀 또는 사용자에게 알림을 발송하여, 수동 개입이 필요한 경우 신속하게 대응할 수 있도록 합니다.

Monitoring#

  • Integration 갱신 실패 모니터링:
text
service:cupixworks-worker status:error "failed to renew integration"
  • BIM360 API 오류율 추적:
text
service:cupixworks-worker status:error "BIM360 refresh_token failed"
  • failed 상태 integration 수 증가 알림 (DB 모니터링 또는 별도 헬스체크 크론)

Risk Assessment#

  • Risk level: low
  • 예상 복잡도: standard
  • 근거: Autodesk Forge API의 일시적 500 에러에 의한 단건 장애이며, 다른 리전/integration에는 영향이 없었습니다. 다만 failed 상태 전환 후 자동 복구 경로가 없어 사용자 영향이 지속될 수 있으므로, 재시도 로직 추가가 권장됩니다.