ES /docs

Fail to create salesforce Facility resource. reason: INVALID_CROSS_REFERENCE_KEY: invalid cross refe

RCA: Fail to create salesforce Facility resource - INVALID_CROSS_REFERENCE_KEY

Error Log#

Datadog Logs

text
Fail to create salesforce Facility resource. reason: INVALID_CROSS_REFERENCE_KEY: invalid cross reference id
RESPONSE: [{"message":"invalid cross reference id","errorCode":"INVALID_CROSS_REFERENCE_KEY","fields":[]}]

Impact#

  • Service: cupixworks-worker
  • 발생 횟수: 1
  • 최초 발생: 2026-04-07T12:56:42.666Z
  • 최근 발생: 2026-04-07T12:56:42.666Z

Root Cause Summary#

Facility ID 19748 ("St Francis BHU Job #1916 Project") 생성 시 Salesforce Project__c 레코드를 만들기 위해 Account__c 필드에 Team의 sf_resource_id를 전달했으나, 해당 값이 Salesforce 측에서 유효하지 않은 Account ID였기 때문에 INVALID_CROSS_REFERENCE_KEY 에러가 발생했습니다. base.rb:17blank? 검증은 값이 nil이거나 빈 문자열인 경우만 차단하므로, Team의 sf_resource_id가 존재하지만 Salesforce에서 삭제되었거나, 다른 org에 속하거나, 접근 권한이 없는 stale한 ID인 경우를 감지하지 못합니다. 이 에러는 7일간 전체 서비스에서 단 1회만 발생한 isolated incident입니다.

Technical Analysis#

Code Path#

  • Entry point: Facility 생성 시 after_commit 콜백이 트리거됩니다.
ruby
# app/models/concerns/salesforce_integratable/facility.rb:14
after_commit :create_salesforce_resource_in_worker, if: :create_salesforce_resource?, on: :create
  • create_salesforce_resource?sf_resource_id.blank?를 확인하여 이미 Salesforce 레코드가 있는지 체크합니다 (facility.rb:16-18).

  • Worker가 비동기로 실행됩니다:

ruby
# app/workers/salesforce_resource_create_worker.rb:5-9
def perform(model_id, model_type)
  model = model_type.constantize.find(model_id)
  facility_resource_id = Cupix::Salesforce::SalesforceService.create_resource!(model)
  model.update!(sf_resource_id: facility_resource_id) if model.respond_to?(:sf_resource_id)
end
  • SalesforceService.create_resource!는 모델 타입에 따라 적절한 클라이언트로 라우팅합니다 (salesforce_service.rb:34-37).

  • Facility ClientAccount__c에 Team의 sf_resource_id를 매핑합니다:

ruby
# app/services/cupix/salesforce/client/facility.rb:10-11
def parent_resource_id(model)
  model.team.sf_resource_id  # Team의 Salesforce Account ID
end
ruby
# app/services/cupix/salesforce/client/facility.rb:14-21
def create_params(model)
  {
    Account__c: parent_resource_id(model),  # 이 값이 유효하지 않음
    Name: model.name,
    Project_ID__c: model.id,
    CupixWorks_Team_Domain__c: model.team.domain
  }
end
  • Failure point: base.rb:17blank? 검증을 통과한 후 Salesforce API 호출에서 실패합니다:
ruby
# app/services/cupix/salesforce/client/base.rb:14-27
def create_resource!(model)
  if parent_resource_id(model).blank?
    Cupix::Logger.warn('Skip creating salesforce resource because parent_resource_id is blank.', ...)
    return  # nil/빈 문자열만 차단 — stale ID는 통과
  end

  begin
    resource_id = @client.create!(sobject_name, create_params(model))
  rescue => e
    Cupix::Logger.error("Fail to create salesforce #{model.class.name} resource. reason: #{e.message}", ...)
  end
end
  • 기대 동작: Team의 sf_resource_id가 Salesforce에서 유효한 Account ID이고, Project__c 레코드가 정상 생성되어야 합니다.
  • 실제 동작: Team의 sf_resource_id가 비어 있지 않으나 Salesforce 측에서 유효하지 않은 ID로 인식되어 INVALID_CROSS_REFERENCE_KEY 에러가 반환되었습니다.

Log Evidence#

사용한 Datadog 쿼리:

text
service:cupixworks-worker "Fail to create salesforce Facility resource" "INVALID_CROSS_REFERENCE_KEY"

핵심 로그 (1건):

json
{
  "timestamp": "2026-04-07T12:56:42.666Z",
  "service": "cupixworks-worker",
  "status": "error",
  "class": "Cupix::Salesforce::Client::Facility",
  "function": "create_resource!",
  "model_type": "Facility",
  "model_id": 19748,
  "model_name": "St Francis BHU Job #1916 Project",
  "tenant": "cupix",
  "host": "ip-10-1-18-233.us-west-2.compute.internal",
  "message": "Fail to create salesforce Facility resource. reason: INVALID_CROSS_REFERENCE_KEY: invalid cross reference id",
  "response": "[{\"message\":\"invalid cross reference id\",\"errorCode\":\"INVALID_CROSS_REFERENCE_KEY\",\"fields\":[]}]"
}

7일간 전체 서비스 대상 INVALID_CROSS_REFERENCE_KEY 검색:

text
"INVALID_CROSS_REFERENCE_KEY"

결과: 위 1건만 발견. 이 에러는 isolated incident입니다.

7일간 Salesforce 관련 에러 패턴 비교:

text
service:cupixworks-worker "salesforce" status:error

결과: 약 100건 이상 발견되었으나, 대부분은 FIELD_CUSTOM_VALIDATION_EXCEPTION: The Account on the Associated Opportunity and the Account on this Project Record Don't Match 에러(Account/Opportunity 불일치)이며, INVALID_CROSS_REFERENCE_KEY는 이 1건만 해당합니다.

이벤트 타임라인:

시간 이벤트
2026-04-07T12:56:42.666Z Cupix::Salesforce::Client::Facility#create_resource! — Facility 19748 Salesforce 생성 실패 (INVALID_CROSS_REFERENCE_KEY)

참고: Salesforce 응답의 fields 배열이 비어 있어, 어떤 필드가 invalid cross reference를 유발했는지 Salesforce가 명시하지 않았습니다. 코드 분석상 Account__c (= model.team.sf_resource_id)가 유일한 cross-reference 필드입니다.

Fix Recommendation#

즉시 조치 (Critical)#

  • 파일: app/services/cupix/salesforce/client/base.rb:17-20
  • parent_resource_id(model).blank? 검증만으로는 stale/invalid Salesforce ID를 감지할 수 없습니다. Salesforce ID 형식 검증(15자 또는 18자 alphanumeric)을 추가하거나, Salesforce API로 ID 존재 여부를 사전 확인하는 로직을 고려해야 합니다.
  • 에러 발생 시 rescue 블록에서 에러를 삼키지 않고, INVALID_CROSS_REFERENCE_KEY인 경우 재시도하거나 알림을 보내는 처리를 추가해야 합니다.

단기 개선 (1주 이내)#

  • Facility 19748이 속한 Team의 sf_resource_id 값을 확인하고, 해당 ID가 Salesforce에 실제로 존재하는지 검증해야 합니다. 만약 Salesforce 측에서 해당 Account가 삭제되었거나 재생성된 경우, Team의 sf_resource_id를 갱신해야 합니다.
  • base.rb:22-27의 rescue 블록에서 에러를 로그만 남기고 조용히 넘기고 있어, resource_id가 nil로 반환됩니다. Worker(salesforce_resource_create_worker.rb:9)는 이 nil 값을 model.update!(sf_resource_id: nil)로 저장하여, Facility의 sf_resource_id가 nil로 남게 됩니다. 에러 시 예외를 재발생시키거나 retry 메커니즘을 도입해야 합니다.

장기 개선 (재발 방지)#

  • Team의 Salesforce Account 동기화 상태를 주기적으로 검증하는 health check를 도입하여, sf_resource_id가 Salesforce에 존재하는지 확인하는 batch job을 추가합니다.
  • Salesforce 연동 에러를 유형별(INVALID_CROSS_REFERENCE_KEY, FIELD_CUSTOM_VALIDATION_EXCEPTION 등)로 분류하여 적절한 자동 복구 또는 알림 처리를 구현합니다.

Monitoring#

  • Salesforce 연동 에러를 유형별로 모니터링:
text
service:cupixworks-worker "Fail to create salesforce" status:error
  • INVALID_CROSS_REFERENCE_KEY 전용 알림:
text
service:cupixworks-worker "INVALID_CROSS_REFERENCE_KEY" status:error
  • Facility sf_resource_id가 nil인 레코드 수를 추적하여, Salesforce 동기화 실패율을 모니터링합니다.

Risk Assessment#

  • Risk level: low
  • 예상 복잡도: standard
  • 이 에러는 7일간 1회만 발생한 isolated incident로, 특정 Team의 stale Salesforce Account ID가 원인으로 추정됩니다. 다만, base.rb의 에러 핸들링이 에러를 삼키는 패턴은 다른 Salesforce 연동 에러에도 공통적으로 적용되므로, 개선 시 전체 Salesforce 연동 안정성이 향상됩니다.