ES /docs

RefinementService::loadCluster | end - not found selected_unrefined_cluster_id

RCA: RefinementService::loadCluster | end - not found selected_unrefined_cluster_id

Overview#

What Happened#

2026-04-20 08:28:46 UTC, cupixworks-capture-refinement-arm-instance 서비스에서 capture 69108에 대한 refinement 작업이 실패했다. Refinement agent가 시작되어 job 179056을 로드한 후, capture의 selected_unrefined_cluster_id 필드가 undefined여서 cluster를 로드하지 못하고 에러로 종료되었다. ap-southeast-2 리전의 tgsp-jv 팀 사용자에게 영향이 있었다.

Quick Facts#

Field Value
exception.message RefinementService::loadCluster | end - not found selected_unrefined_cluster_id
top_frame refinement-service.ts:179
runtime Node.js (filebeat 7.17.15)
env production, ap-southeast-2

Affected Teams#

Team / Domain Error Count Impact
tgsp-jv 1 Capture 69108의 refinement 처리 실패, refinement_state가 error로 전환됨

Timeline#

  1. 08:27:49 UTC — Capture 69108 업데이트, refinement_state draft → ready 전환
  2. 08:28:04 UTCstart_finalizationcreate_editing_entity 호출
  3. 08:28:27 UTC — Capture bulk_operation 실행 (capture 69110 → 69108)
  4. 08:28:29 UTC — Refinement_state ready → queued, refinement job 179056 생성
  5. 08:28:46 UTC — Refinement agent 시작: init → authenticate → run → loadJob
  6. 08:28:46 UTCloadCluster에서 selected_unrefined_cluster_id 누락 에러 발생
  7. 08:28:46 UTCRefinementService::run | end - {} 에러로 catch됨
  8. 08:28:47 UTCterminateService 10초 후 강제 종료 예약
  9. 08:28:49 UTC — Refinement_state queued → error 전환, postprocessor agent 실행

Error Log#

Datadog Logs

text
RefinementService::loadCluster | end - not found selected_unrefined_cluster_id

Impact#

  • Service: cupixworks-capture-refinement-arm-instance
  • Team: tgsp-jv
  • 발생 횟수: 1
  • 최초 발생: 2026-04-20T08:28:46.826Z
  • 최근 발생: 2026-04-20T08:28:46.826Z

Root Cause Summary#

Capture 69108의 selected_unrefined_cluster_id 필드가 설정되지 않은 상태에서 refinement job이 생성되어 실행되었다. Tesla(Rails) 측의 refinement state machine이 ready → queued 전환 시 run_capture_refinement를 호출하여 job을 생성하지만, selected_unrefined_cluster_id가 설정되어 있는지 사전 검증하지 않는다. 이 필드는 API를 통해 외부에서(UI 또는 editing entity flow) 설정되어야 하는데, 이번 케이스에서는 설정되지 않은 채로 refinement가 트리거되었다. Refinement agent의 loadCluster 메서드에서 cpCapture.srvCapture?.selected_unrefined_cluster_idundefined로 평가되어 즉시 에러를 throw했다.

Technical Analysis#

Code Path#

1. Refinement state machine transition (Tesla)

Capture의 refinement_state가 ready → queued로 전환되면 run_capture_refinement가 호출된다.

app/models/concerns/refinementable.rb:55-57ruby
before_transition any => :queued do |model, transition|
  model.run_capture_refinement(model)
end
app/models/concerns/refinementable.rb:128-131ruby
def run_capture_refinement(model)
  capture_invoker = CaptureInvoker.new(model: model, current_user: model.user, current_team: model.team)
  capture_invoker.create_refinement
end

2. Job creation validation (Tesla) — 검증 누락 지점

create_refinementcreate_capture_refinement_invokable?을 호출하지만, 이 validation은 중복 job 존재 여부만 확인한다. selected_unrefined_cluster_id 설정 여부는 검증하지 않는다.

app/models/concerns/invokable/capture.rb:67-71ruby
def create_capture_refinement_invokable?
  if self.jobs.processing.where(kind: 'create_capture_refinement').exists?
    raise Cupix::Errors::Entity.new(code: 'ENT10000', reason: 'Capture has processing create_capture_refinement job')
  end
end
app/invokers/capture_invoker.rb:78-104ruby
def create_refinement(opts = {})
  @model.create_capture_refinement_invokable?

  params = {
    jobable: @model,
    user: self.current_user,
    team: self.current_team
  }
  # ... opts 처리 ...
  job = CreateCaptureRefinementJob.create!(params)
  # selected_unrefined_cluster_id 검증 없이 job 생성
end

3. Refinement agent loadCluster — 실패 지점

Agent가 capture API에서 capture 데이터를 가져온 후, selected_unrefined_cluster_id를 읽으려 하지만 값이 undefined이다.

refinement-service.ts:175-182typescript
private loadCluster = async (cpCapture: CPCapture): Promise<void> => {
	logger.debug('RefinementService::loadCluster | begin');
	const clusterId = cpCapture.srvCapture?.selected_unrefined_cluster_id;
	if (clusterId == undefined) {
		logger.error('RefinementService::loadCluster | end - not found selected_unrefined_cluster_id');
		this.jobManager.setErrorCode(ErrorCode.ScenemapperUtils.RefinerNotFoundCluster);
		throw new Error('not found selected_unrefined_cluster_id');
	}
	// ...
};

srvCaptureTESLA.Capture API 응답 객체이며, selected_unrefined_cluster_id 타입은 number | undefined이다.

cpcapture.ts:40typescript
get srvCapture(): TESLA.Capture | undefined { return this._serverModel; }

4. selected_unrefined_cluster_id 설정 경로 (Tesla)

이 필드는 capture의 sys JSON 컬럼에 저장되며, update_selected_unrefined_cluster_id 메서드를 통해서만 설정된다.

app/concerns/parameter/capture.rb:270-279ruby
def update_selected_unrefined_cluster_id(params)
  return if params[:selected_unrefined_cluster_id].blank?

  _cluster = ::Cluster.find(params[:selected_unrefined_cluster_id])
  if _cluster.present? && _cluster.cluster_type != 'unrefined'
    raise Cupix::Errors::Parameter.new(code: 'ARG10001', reason: 'selected_unrefined_cluster_id must be unrefined cluster')
  end

  @model.selected_unrefined_cluster_id = params[:selected_unrefined_cluster_id]
end

이 메서드는 capture update API 호출 시 파라미터로 전달될 때만 실행된다. 자동으로 설정되는 경로가 없다.

5. reset_refinement에서 nil로 초기화

app/invokers/capture_invoker.rb:315ruby
def reset_refinement
  @model.selected_unrefined_cluster_id = nil
  # ...
end

Log Evidence#

Datadog에서 capture 69108의 전체 실행 흐름을 추적한 결과:

text
service:cupixworks-capture-refinement-arm-instance @capture.id:69108
text
08:28:46.506Z [info]  RefinementService::init
08:28:46.507Z [info]  RefinementService::authenticate | begin
08:28:46.554Z [info]  CupixAuth::setSession | session_id: 691ccee0bf047b658f2a888cf7db01dae6e38b18
08:28:46.554Z [info]  RefinementService::authenticate | end
08:28:46.555Z [info]  RefinementService::run | begin
08:28:46.555Z [info]  JobManager::loadJob | begin - job id: 179056
08:28:46.595Z [info]  JobManager::loadJob | end - job id: 179056
08:28:46.826Z [error] RefinementService::loadCluster | end - not found selected_unrefined_cluster_id
08:28:46.826Z [error] RefinementService::run | end - {}
08:28:47.625Z [info]  RefinementService::terminateService | force shutdown after 10 seconds

Agent가 init → authenticate → run → loadJob까지 정상 실행 후, loadCluster에서 즉시 실패했다. loadJob에서 run까지 0.23초 만에 에러가 발생하여, API에서 capture 데이터를 가져온 직후 selected_unrefined_cluster_id가 없음을 확인한 것이다.

Tesla API 측 로그:

text
service:cupixworks-api 69108
text
08:27:50Z [info]  refinement_state has transitioned from draft to ready on Capture 69108
08:28:04Z [info]  create editing entity on start finalization Capture 69108
08:28:04Z [info]  create editing entity on Capture 69108
08:28:29Z [info]  Refinement job is created for capture 69108. job_id: 179056
08:28:29Z [info]  refinement_state has transitioned from ready to queued on Capture 69108
08:28:49Z [info]  refinement_state has transitioned from queued to error on Capture 69108

selected_unrefined_cluster_id 설정에 대한 로그가 전혀 없다. Refinement state가 draft → ready → queued로 자동 전환되면서 job이 생성되었지만, cluster ID는 외부에서 설정되지 않았다.

Hypotheses Considered#

# Hypothesis Evidence for Evidence against Verdict
H1 selected_unrefined_cluster_id가 설정되지 않은 상태에서 refinement job이 생성됨 — Tesla 측 validation 부재 create_capture_refinement_invokable?가 중복 job만 검증 (invokable/capture.rb:67-71). API 로그에 selected_unrefined_cluster 설정 관련 기록 없음. Agent 로그에서 loadCluster 즉시 실패 Confirmed
H2 API 응답에서 selected_unrefined_cluster_id 필드가 누락됨 (serialization 문제) capture_serializer.rb:51에서 해당 필드를 attribute로 포함하고 있음. 필드가 nil이면 API 응답에서 undefined/null로 반환되는 것이 정상 동작 Rejected
H3 reset_refinement이 호출되어 이미 설정된 cluster ID가 초기화됨 reset_refinement에서 selected_unrefined_cluster_id = nil 설정 (capture_invoker.rb:315) API 로그에서 reset_refinement 호출 기록 없음. Refinement state가 draft → ready → queued로 순차 전환되어 reset이 개입하지 않음 Rejected
H4 Editing entity done callback에서 cluster ID 설정 없이 queued 전환 트리거 done_state_callback_editing_entityqueued_refinement_state!만 호출하고 cluster ID를 설정하지 않음 (editing_entity.rb:190-196). 로그에서 editing entity 생성 후 자동으로 queued 전환 확인 Confirmed

Fix Recommendation#

즉시 조치 (Critical)#

  • app/models/concerns/invokable/capture.rb:67-71create_capture_refinement_invokable? 메서드에 selected_unrefined_cluster_id 존재 여부 검증을 추가해야 한다.
  • 해당 필드가 nil이면 refinement job 생성을 차단하여, agent가 불필요하게 실행되는 것을 방지한다.

단기 개선 (1주 이내)#

  • app/models/concerns/refinementable.rb:55-57before_transition any => :queued 훅에서 selected_unrefined_cluster_id가 설정되어 있는지 확인하는 guard를 추가해야 한다. 설정되지 않은 경우 transition을 거부하거나 에러 상태로 전환한다.
  • app/models/concerns/finalization/editing_entity.rb:190-196done_state_callback_editing_entity에서 queued_refinement_state! 호출 전에 selected_unrefined_cluster_id가 설정되어 있는지 확인해야 한다.

장기 개선 (재발 방지)#

  • Refinement 워크플로우에서 selected_unrefined_cluster_id 자동 설정 메커니즘을 검토할 필요가 있다. 현재는 외부 API 호출에 의존하므로, editing entity flow에서 자동으로 적절한 unrefined cluster를 선택하는 로직을 고려한다.
  • Capture model에 refinement 관련 필수 필드에 대한 custom validation을 추가하여, state transition 시 데이터 정합성을 보장한다.

Monitoring#

  • selected_unrefined_cluster_id 누락으로 인한 실패를 추적하는 메트릭 추가
  • Datadog 쿼리 예시:
text
service:cupixworks-capture-refinement-arm-instance "not found selected_unrefined_cluster_id" status:error
  • Refinement job 생성 시 selected_unrefined_cluster_id nil 비율 모니터링:
text
service:cupixworks-api "Refinement job is created" -"selected_unrefined_cluster"

Risk Assessment#

  • Risk level: medium
  • 예상 복잡도: standard — Tesla 측 validation 추가가 주요 변경점이며, refinement agent 코드 변경은 불필요하다.