ES /docs

ProgressSerializer attribute block arity mismatch with fast_jsonapi

RCA: Index error - wrong number of arguments (given 1, expected 0)

Overview#

What Happened#

2026-06-06 ~ 06-07 기간 동안 cupixworks-worker의 FinalizeTransferWorker에서 Progress 모델의 Elasticsearch 인덱싱 시 ArgumentError: wrong number of arguments (given 1, expected 0) 에러가 13회 발생했다. Transfer 작업 완료 후 Progress 레코드를 ES에 색인하는 과정에서 ProgressSerializer의 잘못된 attribute 선언(&:record_complete_at)이 fast_jsonapi의 arity 기반 dispatch 로직과 비호환하여 에러가 발생하는 latent bug이다.

Quick Facts#

Field Value
exception.class ArgumentError
exception.message wrong number of arguments (given 1, expected 0)
top_frame app/models/concerns/properties/progress.rb:91
runtime Ruby 3.3.7 / Rails 7.2.2 / fast_jsonapi 1.5
deploy production-us-west-2-20260605T0515Z0-8562b202-cupixworks
env production, us-west-2

Affected Teams#

Team / Domain Error Count Impact
Transfer / Facility 13 Progress 문서 Elasticsearch 인덱싱 실패, 검색 결과 불일치

Timeline#

  1. 2026-06-06 20:16 KST — 최초 발생 (Transfer 837, JID cb1e660c35a5b7a2b1e36d75)
  2. 2026-06-06 21:00 ~ 22:59 KST — 동일 패턴 반복 발생 (Transfer 840~844)
  3. 2026-06-07 21:57 KST — 최근 발생 (Transfer 845, JID ba61aaf2361597657d8fb643)
  4. 2026-06-07 KST — Error Sweeper 감지 및 RCA 분석 완료

Error Log#

Datadog Logs

text
Index error - wrong number of arguments (given 1, expected 0)

Impact#

  • Service: cupixworks-worker
  • 발생 횟수: 13
  • 최초 발생: 2026-06-06 20:16 KST
  • 최근 발생: 2026-06-07 21:57 KST
  • 영향 범위: FinalizeTransferWorker가 Sidekiq retry 1회 후 dead 처리됨. Transfer 대상 시설의 Progress 데이터가 Elasticsearch에 색인되지 않아 검색/조회에 누락 가능.

Root Cause Summary#

ProgressSerializer에서 attribute :record_completed_at, &:record_complete_at 선언이 fast_jsonapi 1.5의 직렬화 로직과 호환되지 않는 것이 근본 원인이다. Symbol#to_proc의 arity가 -2(abs=2)이기 때문에, fast_jsonapi의 Attribute#serializemethod.arity.abs == 1 조건이 false가 되어 method.call(record, serialization_params) 분기로 진입한다. 이는 record.record_complete_at(serialization_params)로 변환되지만, record_complete_at 메서드는 인자를 받지 않으므로 ArgumentError가 발생한다. 이 버그는 FinalizeTransferWorker가 Progress 레코드를 업데이트하여 after_commit_update_document_index_documentas_indexed_jsonProgressSerializer.serializable_hash 경로가 실행될 때 트리거된다.

Technical Analysis#

Code Path#

  • Entry point: app/workers/finalize_transfer_worker.rb:5perform(transfer_id)
  • app/models/concerns/transferable.rb:155run_set_transfer_descendants(target_model, transfer_model)
  • app/models/concerns/transferable/facility.rb:66update!(workspace: target_model, team: ...)로 Progress 레코드에 변경 발생
  • app/models/concerns/searchable.rb:16-18after_commit on: [:update]_update_document
  • app/models/concerns/searchable.rb:107-110attributes_in_database nil → fallback to _index_document
  • app/models/concerns/searchable.rb:37__elasticsearch__.as_indexed_json 호출
  • app/models/concerns/searchable/progress.rb:232-278as_indexed_jsonProgressSerializer.new(self, params).serializable_hash
  • fast_jsonapi/attribute.rb:13-14 — arity 체크 후 method.call(record, serialization_params)
  • Failure point: app/models/concerns/properties/progress.rb:91def record_complete_at (0 args, 1 given)

Serializer 선언 (문제 지점):

app/serializers/progress_serializer.rb:91ruby
attribute :record_completed_at, &:record_complete_at

attribute 이름 :record_completed_at와 메서드명 :record_complete_at가 다르기 때문에 &:method 형태로 block을 넘기고 있다. 비교: line 92의 attribute :record_undetected_at는 이름과 메서드가 동일하여 block 없이 선언되어 정상 동작한다.

fast_jsonapi의 attribute 직렬화 로직:

fast_jsonapi-1.5/lib/fast_jsonapi/attribute.rb:11-18ruby
def serialize(record, serialization_params, output_hash)
  if include_attribute?(record, serialization_params)
    output_hash[key] = if method.is_a?(Proc)
      method.arity.abs == 1 ? method.call(record) : method.call(record, serialization_params)
    else
      record.public_send(method)
    end
  end
end

&:record_complete_atSymbol#to_proc으로 변환된다:

Ruby 3.3 Symbol#to_proc arity verificationruby
:record_complete_at.to_proc.arity  # => -2  (abs = 2)

따라서 method.arity.abs == 1이 false → method.call(record, serialization_params) 분기 실행 → record.record_complete_at(serialization_params) → ArgumentError.

대상 메서드 (0개 인자):

app/models/concerns/properties/progress.rb:91-93ruby
def record_complete_at
  self.sys[:record_complete_at]
end

에러 catch 후 BulkIndexWorker로 위임 (동일 에러 재발생):

app/models/concerns/searchable.rb:50-52ruby
rescue StandardError => e
  Cupix::Logger.error("Index error - #{e.message}", class: self.class.name, function: __method__)
  BulkIndexWorker.perform_async(self.class.name, [id], 'index')
end

BulkIndexWorker도 동일 as_indexed_json 경로를 타므로 같은 에러가 반복된다.

Log Evidence#

Datadog 검색 쿼리:

text
service:cupixworks-worker status:error @environment:production "wrong number of arguments (given 1, expected 0)"

에러 로그 패턴 (각 transfer job마다 쌍으로 발생):

json
{
  "message": "Index error - wrong number of arguments (given 1, expected 0)",
  "class": "Progress",
  "function": "_index_document"
}
json
{
  "message": "StandardError - wrong number of arguments (given 1, expected 0)",
  "class": "Progress",
  "function": "_update_document"
}

선행 warn 로그 (_update_document에서 fallback 트리거):

json
{
  "message": "NotFound - attributes_in_database",
  "class": "Progress",
  "function": "_update_document",
  "level": "warn"
}

SidekiqDeathHandler에서 캡처된 full stack trace:

text
/var/app/current/app/models/concerns/properties/progress.rb:91:in `record_complete_at'
/var/app/current/vendor/bundle/ruby/3.3.0/gems/fast_jsonapi-1.5/lib/fast_jsonapi/attribute.rb:14:in `serialize'
/var/app/current/vendor/bundle/ruby/3.3.0/gems/fast_jsonapi-1.5/lib/fast_jsonapi/serialization_core.rb:48:in `block in attributes_hash'
/var/app/current/vendor/bundle/ruby/3.3.0/gems/fast_jsonapi-1.5/lib/fast_jsonapi/serialization_core.rb:47:in `each_with_object'
/var/app/current/vendor/bundle/ruby/3.3.0/gems/fast_jsonapi-1.5/lib/fast_jsonapi/serialization_core.rb:47:in `attributes_hash'
/var/app/current/vendor/bundle/ruby/3.3.0/gems/fast_jsonapi-1.5/lib/fast_jsonapi/serialization_core.rb:80:in `record_hash'
/var/app/current/vendor/bundle/ruby/3.3.0/gems/fast_jsonapi-1.5/lib/fast_jsonapi/object_serializer.rb:46:in `hash_for_one_record'
/var/app/current/vendor/bundle/ruby/3.3.0/gems/fast_jsonapi-1.5/lib/fast_jsonapi/object_serializer.rb:35:in `serializable_hash'
/var/app/current/app/models/concerns/searchable.rb:588:in `to_searchable_json'

영향받은 FinalizeTransferWorker jobs:

text
Transfer 837 (JID: cb1e660c35a5b7a2b1e36d75) - 2026-06-06T11:16:07Z
Transfer 840 (JID: 6f5b9d9f92751ebed04962f1) - 2026-06-06T12:00:46Z
Transfer 841 (JID: 7400745ce645c413ee6528a5) - 2026-06-06T13:14:06Z
Transfer 842 (JID: 71fc6e70a9afc0a33f37efc3) - 2026-06-06T13:22:38Z
Transfer 843 (JID: 2c9b9f91ec2a561ab95cfe18) - 2026-06-06T13:47:42Z
Transfer 844 (JID: 42896058715ac680bc9a0515) - 2026-06-06T13:58:32Z
Transfer 845 (JID: ba61aaf2361597657d8fb643) - 2026-06-07T12:57:11Z

Hypotheses Considered#

# Hypothesis Evidence for Evidence against Verdict
H1 &:record_complete_at이 fast_jsonapi의 arity 체크와 호환되지 않아 serialization_params를 0-arg 메서드에 전달 Symbol#to_proc.arity == -2 (Ruby 3.3), attribute.rb:14에서 arity.abs != 1call(record, params) 실행, 스택 트레이스가 attribute.rb:14progress.rb:91 확인 Confirmed
H2 record_complete_at 메서드 시그니처가 최근 변경됨 git log에서 progress.rb 해당 메서드는 TSLA-5741 이후 변경 없음, 항상 0-arg Rejected
H3 Ruby 버전 업그레이드로 Symbol#to_proc arity 동작 변경 Symbol#to_proc.arity == -2는 Ruby 2.x부터 동일, 최근 .ruby-version 변경 이력 없음 Rejected
H4 이전에는 이 코드 경로가 실행되지 않았고, Transfer 사용 증가로 latent bug 노출 에러가 2026-06-06에 처음 감지됨. Serializer 코드는 오래전 작성됨. Transfer를 통해 Progress ES 인덱싱이 트리거되는 경로가 최근 사용됨 serializer는 API response에서도 사용되나, API 경로에서는 fields 제한으로 record_completed_at 필드가 포함되지 않을 수 있음 Confirmed

Fix Recommendation#

즉시 조치 (Critical)#

  • 파일: app/serializers/progress_serializer.rb:91
  • &:record_complete_at를 block 형식으로 변경하여 fast_jsonapi가 arity=1 경로로 호출하도록 수정:
app/serializers/progress_serializer.rb:91
-  attribute :record_completed_at, &:record_complete_at+  attribute :record_completed_at do |progress|+    progress.record_complete_at+  end
  • 수정 후 dead job 재시도 필요: Transfer 837~845에 대해 Progress ES 재색인

단기 개선 (1주 이내)#

  • ProgressSerializer에서 &:method_name 패턴을 사용하는 다른 attribute들 (&:_user, &:_level, &:_bim, &:_bim_revision, &:_sketch, &:_activity, &:_vendor, &:_category) 검토. 이들 메서드가 *args를 수용하는지 확인하여 동일 latent bug 방지
  • BulkIndexWorker의 retry 무한 루프 방지: 동일 에러가 재발할 경우 exponential backoff + 최대 재시도 제한 추가

장기 개선 (재발 방지)#

  • fast_jsonapi gem은 archived/deprecated 상태. jsonapi-serializer (community fork)로 마이그레이션 검토
  • Serializer 단위 테스트 추가: 모든 serializer에서 실제 모델 인스턴스를 직렬화하는 테스트를 작성하여 arity 불일치를 CI에서 사전 감지

Monitoring#

  • Progress 인덱싱 실패 알림:
text
service:cupixworks-worker status:error "Index error" @class:Progress
  • FinalizeTransferWorker dead job 모니터링:
text
service:cupixworks-worker status:error "died" "FinalizeTransferWorker"

Risk Assessment#

  • Risk level: medium
  • 예상 복잡도: trivial — serializer attribute 선언 방식 1줄 변경으로 해결 가능