ES /docs

error on starting sitetrack on record_id: 6277 - Updating entity on billing expired team/workspace i

RCA: error on starting sitetrack on record_id: 6277 - Updating entity on billing expired team/workspace

Overview#

What Happened#

2026-04-30 19:47~19:53 UTC 사이에 cupixworks-api (eu-central-1)에서 Editing 상태 전환 후 자동 sitetrack 생성이 2회 실패했다. Team "spen" (id: 62)의 billing이 만료된 상태에서 capture processing이 완료되어 sitetrack 생성이 트리거되었으나, BaseFactory#check_updatable_by_billing_state!에서 PERM33000 에러로 거부되었다.

Quick Facts#

Field Value
exception.class Cupix::Errors::PermissionDenied
exception.message Updating entity on billing expired team/workspace is not allowed
top_frame lib/cupix/abstract/base.rb:38
deploy production-eu-central-1-20260430T1710Z0-768ef1f7-cupixworks
env production, eu-central-1

Affected Teams#

Team / Domain Error Count Impact
spen (id: 62) 2 Sitetrack 자동 생성 실패 — 사용자가 수동으로 재시도해도 billing 만료로 인해 동일 실패 발생

Timeline#

  1. 2026-04-30T19:47:05Z — Capture 33627, 33628, 33629 processing 완료, [Record] applied_license: expired on 6277 로그
  2. 2026-04-30T19:47:11Z — Editing done 전환 → _perform_sitetrack_start → SitetrackFactory 생성 시도 → PERM33000 에러 (1차)
  3. 2026-04-30T19:53:23Z — Capture 33630 processing 완료, billing 만료 확인
  4. 2026-04-30T19:53:25Z — 동일 flow 재실행 → PERM33000 에러 (2차)

Error Log#

Datadog Logs

text
error on starting sitetrack on record_id: 6277 - Updating entity on billing expired team/workspace is not allowed

Impact#

  • Service: cupixworks-api
  • 발생 횟수: 2
  • 최초 발생: 2026-04-30T19:47:11.582Z
  • 최근 발생: 2026-04-30T19:53:25.736Z

Root Cause Summary#

Team "spen" (id: 62)의 billing이 만료된 상태에서 capture processing이 완료되면 Editing 상태가 done으로 전환되고, after_transition hook이 자동으로 start_sitetrack을 호출한다. 이 과정에서 SitetrackFactory#create!BaseFactory#create!check_updatable_by_billing_state!가 실행되는데, Sitetrack 모델의 applied_billing_state'expired'를 반환하므로 PERM33000 에러가 발생한다. 문제의 핵심은 billing 상태를 사전 검증하지 않고 sitetrack 생성을 시도하는 것이다. _perform_sitetrack_start는 editing 유효성, 기존 sitetrack 존재 여부, video capture 존재 여부는 체크하지만 billing 상태는 체크하지 않는다.

Technical Analysis#

Code Path#

  • Entry point: app/models/concerns/statable/editing.rb:159-161after_transition to: :done hook
app/models/concerns/statable/editing.rb:159-161ruby
after_transition from: any, to: :done do |model, transition|
  model.start_sitetrack
end
  • start_sitetrack (line 226)에서 facility 조건 검증 후 _perform_sitetrack_start 호출 (line 255)
app/models/concerns/statable/editing.rb:253-255ruby
if sitetrack_startable
  Cupix::Logger.info("All conditions met. Calling StartSitetrackWorker for Editing #{self.id}", class_name: self.class.name, function: __method__)
  _perform_sitetrack_start
end
  • _perform_sitetrack_start (line 262)에서 기본 검증 수행 후 SitetrackFactory.create! 호출 (line 301)
app/models/concerns/statable/editing.rb:299-308ruby
Cupix::Logger.info("start sitetrack on record_id: #{self.record_id}", class: self.class.name, function: __method__, record: { id: self.record_id }, level_id: self.level_id)
begin
  sitetrack = ::SitetrackFactory.new(current_user: self.user, current_team: self.team).create!({
    record_id: self.record_id,
    level_id: self.level_id
  })
  Cupix::Logger.info("Sitetrack automatically created for record_id: #{self.record_id}, level_id: #{self.level_id}, sitetrack_id: #{sitetrack.id}", class: self.class.name, function: __method__, record: { id: self.record_id }, level_id: self.level_id, sitetrack: { id: sitetrack.id })
rescue StandardError => e
  Cupix::Logger.error("error on starting sitetrack on record_id: #{self.record_id} - #{e.message}", class: self.class.name, function: __method__, record: { id: self.record_id }, level_id: self.level_id)
  return
end
  • SitetrackFactory#create! (line 5-37)에서 모델 초기화 후 superBaseFactory#create! 호출
app/factories/sitetrack_factory.rb:5-6ruby
def create!(params = {})
  self.model = ::Sitetrack.new(team_id: self.current_team.id)
  • Failure point: BaseFactory#create! line 84 → check_updatable_by_billing_state!
app/factories/base_factory.rb:80-84ruby
def create!(params = {})
  raise Cupix::Errors::Unauthorized.new(code: 'ARG10000', reason: 'current_user is required') if self.current_user.nil? && current_user_required?

  check_archived_entity
  check_updatable_by_billing_state!
  • 실제 billing 검증 로직에서 applied_billing_state == 'expired' 조건에 해당되어 예외 발생
lib/cupix/abstract/base.rb:37-38ruby
if self.model.applied_billing_state == 'expired'
  raise Cupix::Errors::PermissionDenied.new(code: 'PERM33000', reason: 'Updating entity on billing expired team/workspace is not allowed')
end
  • Sitetrack 모델은 FacilityEntityFacilityEntity::Billing을 include하여 team/workspace/facility billing 상태를 종합 판단
app/models/concerns/facility_entity/billing.rb:52-59ruby
def applied_billing_state
  billing_states = [team_billing_state, workspace_billing_state, facility_billing_state]

  return 'active' if billing_states.include?('active')
  return 'expired' if billing_states.include?('expired')

  'none'
end

Log Evidence#

사용한 Datadog 쿼리:

text
service:cupixworks-api status:error @environment:production "error on starting sitetrack on record_id: 6277"
text
service:cupixworks-api @environment:production "6277"

에러 발생 직전 로그 시퀀스 (1차, 19:47:11Z):

text
[INFO] Capture 33627 processing is completed. record_id:6277 (class: Capture, function: notify_processing_completed)
[INFO] skipped to push processing_completed notification for record:6277. skip_reason: unfinalized capture exists (unfinalized: [33627, 33629, 33630])
[INFO] Capture 33629 processing is completed. record_id:6277
[INFO] [Record] applied_license: expired on 6277
[INFO] start sitetrack on record_id: 6277 (class: Editing, function: _perform_sitetrack_start)
[ERROR] error on starting sitetrack on record_id: 6277 - Updating entity on billing expired team/workspace is not allowed

에러 발생 직전 로그 시퀀스 (2차, 19:53:25Z):

text
[INFO] Capture 33630 processing is completed. record_id:6277 (class: Capture, function: notify_processing_completed)
[ERROR] skipped to push processing_completed notification by Billing has expired on Team for record:6277 (error_code: BILL6000)
[INFO] [Record] applied_license: expired on 6277
[INFO] start sitetrack on record_id: 6277 (class: Editing, function: _perform_sitetrack_start)
[ERROR] error on starting sitetrack on record_id: 6277 - Updating entity on billing expired team/workspace is not allowed

핵심 관찰: [Record] applied_license: expired on 6277 로그가 매번 에러 직전에 출력되어 billing 만료 상태가 이미 감지되고 있음에도 sitetrack 생성 시도가 진행됨.

Hypotheses Considered#

# Hypothesis Evidence for Evidence against Verdict
H1 Billing 만료 상태에서 sitetrack 자동 생성 시도 시 사전 검증 부재 _perform_sitetrack_start에 billing 체크 없음 (editing.rb:262-314). 로그에서 applied_license: expired 확인 후에도 생성 시도 진행. BaseFactory에서 PERM33000 발생 Confirmed
H2 Billing 상태가 일시적으로 잘못 계산됨 (race condition) 동일 record에 대해 시간 간격(6분)을 두고 2회 동일 에러 발생. 로그에서 일관되게 expired 상태 보고. 사용자 API 호출(GET /records/6277)도 정상 200 반환하여 read는 허용됨 Rejected
H3 Capture processing 완료 이벤트가 잘못된 타이밍에 발생 Capture 33627-33630이 짧은 시간 내 완료되어 여러 번 트리거됨 Editing done 전환은 정상적 flow이며, 각 capture 완료 시 editing이 done으로 전환될 수 있음. 타이밍이 아닌 billing 상태가 문제 Rejected

Fix Recommendation#

즉시 조치 (Critical)#

  • 파일: app/models/concerns/statable/editing.rb:262 (_perform_sitetrack_start 메서드)
  • 방향: Sitetrack 생성 시도 전에 billing 상태를 먼저 확인하여, expired인 경우 info 레벨로 로깅하고 조기 return. 이는 예상 가능한 비즈니스 시나리오이므로 error 레벨 로그가 아닌 info 또는 warn으로 처리해야 한다.

단기 개선 (1주 이내)#

  • start_sitetrack 메서드(line 226)의 sitetrack_startable 조건에 billing 상태 검증을 추가하여 _perform_sitetrack_start 진입 전에 차단. 이렇게 하면 불필요한 DB 쿼리(existing sitetrack 조회, video captures 조회)도 방지할 수 있다.

장기 개선 (재발 방지)#

  • Capture processing 완료 → Editing done 전환 → sitetrack 생성 파이프라인 전체에서 billing 만료 팀의 자동 처리를 일관되게 스킵하는 정책 수립. notify_processing_completed에서는 이미 BILL6000으로 차단하고 있으므로, sitetrack 생성도 동일 패턴을 따르도록 통일.

Monitoring#

추가할 메트릭/알림:

text
service:cupixworks-api status:error "error on starting sitetrack" "billing expired"
  • Billing 만료 팀에서의 sitetrack 생성 시도 빈도를 추적하는 custom metric 추가 고려
  • 현재 발생 빈도(2회)가 낮으므로 즉각적 알림보다는 주간 리포트에 포함하는 수준이 적절

Risk Assessment#

  • Risk level: low
  • 예상 복잡도: trivial