ES /docs

Error occurred during comparing location longitude.

RCA: Error occurred during comparing location longitude

Error Log#

Datadog Logs

text
Error occurred during comparing location longitude.

Impact#

  • Service: cupixworks-api
  • 발생 횟수: 2
  • 최초 발생: 2026-04-08T22:31:44.083Z
  • 최근 발생: 2026-04-08T22:31:44.083Z

Root Cause Summary#

Facility 모델의 _location_field_changed? 메서드(geo_queriable.rb:66-75)가 saved_changes['sys']에서 이전/이후 longitude 값을 읽어 산술 연산(-)을 수행할 때, 해당 값이 Float가 아닌 String 타입으로 저장되어 있어 undefined method '-' for an instance of String 에러가 발생했습니다. latitude/longitude getter 메서드는 to_f!로 타입 변환을 수행하지만, _location_field_changed?saved_changes hash에서 raw JSONB 값을 직접 읽기 때문에 타입 변환 없이 String 값이 그대로 사용됩니다.

Technical Analysis#

Code Path#

  • Entry point: app/models/concerns/geo_queriable/facility.rb:12update_timezone callback이 before_commit :update_timezone, on: :update로 등록됨
  • update_timezonelocation_changed?를 호출하여 위치 변경 여부를 판단
ruby
# app/models/concerns/geo_queriable/facility.rb:8-17
before_commit :update_timezone, on: :update

private

def update_timezone
  return unless location_changed?

  Cupix::Logger.info("update timezone on facility #{self.key}.", class: self.class.name, function: __method__, facility: { id: self.id, key: self.key, saved_changes: saved_changes })
  self.set_location_based_timezone
end
  • location_changed? 메서드(geo_queriable.rb:57-62)가 _location_field_changed?('longitude')를 호출
ruby
# app/models/concerns/geo_queriable.rb:57-62
def location_changed?
  return false if saved_changes.blank?
  return false unless saved_changes.keys.include?('sys')

  _location_field_changed?('latitude') || _location_field_changed?('longitude')
end
  • Failure point: app/models/concerns/geo_queriable.rb:70(before - after).abs에서 String에 대해 - 메서드 호출 시 에러 발생
ruby
# app/models/concerns/geo_queriable.rb:66-75
def _location_field_changed?(field)
  begin
    before = saved_changes['sys'][0].try(:[], field) || 0
    after = saved_changes['sys'][1].try(:[], field) || 0
    (before - after).abs > LOCATION_COMPARE_THRESHOLD
  rescue StandardError => e
    Cupix::Logger.error("Error occurred during comparing location #{field}.", class: self.class.name, function: __method__, error: e, before: before, after: after)
    false
  end
end

기대 동작: saved_changes['sys'][0][field]saved_changes['sys'][1][field]가 Float(또는 nil → 0) 값이어서 (before - after).abs가 정상적으로 산술 연산을 수행하고, LOCATION_COMPARE_THRESHOLD(0.001)와 비교하여 위치 변경 여부를 판단합니다.

실제 동작: saved_changes['sys']는 PostgreSQL JSONB 컬럼의 raw 변경 이력을 반환합니다. JSONB에 longitude 값이 String 형태(예: "174.75330")로 저장되어 있는 경우, before 또는 after가 String이 되어 - 메서드가 존재하지 않아 NoMethodError: undefined method '-' for an instance of String 에러가 발생합니다.

반면 latitude/longitude getter 메서드는 to_f!를 사용하여 타입 변환을 수행합니다:

ruby
# app/models/concerns/geo_queriable.rb:31-33
def longitude
  (self.sys[:longitude] || nil)&.to_f!
end

to_f!config/initializers/type_checker.rb:2-5에 정의된 커스텀 메서드로, Float(self)를 호출하여 String을 Float로 변환합니다:

ruby
# config/initializers/type_checker.rb:2-5
def to_f!
  Float(self)
rescue
  raise Cupix::Errors::Parameter.new(code: 'ARG10062', message: "Invalid type: #{self}, required float format")
end

_location_field_changed?는 이 getter를 사용하지 않고 saved_changes['sys'] hash에서 직접 값을 읽기 때문에 타입 변환이 누락됩니다.

Log Evidence#

Datadog에서 검색한 에러 로그 2건은 동일한 시각에 발생했으며, 모두 동일한 에러 메시지를 포함합니다.

사용한 Datadog 쿼리:

text
service:cupixworks-api status:error "Error occurred during comparing location longitude"
Time range: 2026-04-08T21:30:00Z to 2026-04-08T23:00:00Z

핵심 로그 항목:

json
{
  "timestamp": "2026-04-09 07:31:44 KST",
  "status": "error",
  "message": "Error occurred during comparing location longitude.",
  "class": "Facility",
  "function": "_location_field_changed?",
  "error": {
    "msg": "undefined method `-' for an instance of String"
  }
}

타임라인:

  • 2026-04-08T22:31:44Z — Facility 업데이트 시 before_commit callback에서 location_changed?_location_field_changed?('longitude') 호출
  • 동일 시각에 2건 발생 — 동일 Facility에 대한 연속적인 업데이트이거나, 2개의 서로 다른 Facility 업데이트로 추정

추가 검색 — warn 레벨에서 latitude/longitude type 관련 경고 검색:

text
service:cupixworks-api status:warn "Invalid" "type" "longitude" OR "latitude"
Time range: 2026-04-01T00:00:00Z to 2026-04-09T00:00:00Z

해당 시간대에 Facility 관련 type 경고는 발견되지 않았습니다. 이는 latitude/longitude setter를 통해 저장된 값이 아니라, 이전에 다른 경로(예: 마이그레이션, 데이터 임포트, 이전 버전 코드)로 JSONB에 String으로 저장된 historical 데이터가 원인일 가능성을 시사합니다.

Fix Recommendation#

즉시 조치 (Critical)#

  • 파일: app/models/concerns/geo_queriable.rb:68-69
  • _location_field_changed? 메서드에서 beforeafter 값을 읽은 후 to_f 변환을 추가해야 합니다. saved_changes['sys']에서 가져온 값이 String, Integer, Float 등 다양한 타입일 수 있으므로, 산술 연산 전에 안전하게 Float로 변환해야 합니다. to_f! 대신 to_f를 사용하여 변환 실패 시 0.0을 반환하도록 하는 것이 안전합니다 (이미 rescue 블록이 있지만, 예방적 방어가 더 적절합니다).

단기 개선 (1주 이내)#

  • sys JSONB 컬럼에 String으로 저장된 latitude/longitude 값이 있는 Facility 레코드를 조회하여 Float로 변환하는 데이터 마이그레이션을 실행해야 합니다. 이는 동일 에러의 재발을 방지합니다.
  • saved_changes에서 sys 값을 읽는 다른 코드 경로가 있는지 확인하고, 유사한 타입 불일치 문제가 없는지 점검해야 합니다.

장기 개선 (재발 방지)#

  • sys JSONB 컬럼에 저장되는 latitude/longitude 값에 대해 데이터베이스 레벨의 타입 검증(CHECK constraint 또는 Rails validation)을 추가하여 String 값이 저장되는 것을 원천적으로 방지해야 합니다.
  • _location_field_changed?처럼 saved_changes에서 raw 값을 읽는 패턴은 타입 안전성 문제가 있으므로, JSONB 값 비교를 위한 공통 유틸리티 메서드를 도입하는 것을 고려할 수 있습니다.

Monitoring#

  • 동일 에러 재발 모니터링:
text
service:cupixworks-api status:error "Error occurred during comparing location"
  • latitude/longitude type 경고 모니터링:
text
service:cupixworks-api status:warn "Invalid latitude type" OR "Invalid longitude type"

Risk Assessment#

  • Risk level: low
  • 예상 복잡도: trivial
  • 에러가 rescue 블록에서 catch되어 false를 반환하므로 API 요청 자체는 실패하지 않습니다. 다만 location_changed?가 항상 false를 반환하게 되어 timezone 업데이트가 누락될 수 있습니다. 영향 범위는 sys JSONB에 String 타입 longitude/latitude 값을 가진 Facility로 제한됩니다.