Error occurred during comparing location longitude.
RCA: Error occurred during comparing location longitude
Error Log#
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:12—update_timezonecallback이before_commit :update_timezone, on: :update로 등록됨 update_timezone은location_changed?를 호출하여 위치 변경 여부를 판단
# 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')를 호출
# 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에 대해-메서드 호출 시 에러 발생
# 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!를 사용하여 타입 변환을 수행합니다:
# 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로 변환합니다:
# 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 쿼리:
service:cupixworks-api status:error "Error occurred during comparing location longitude"
Time range: 2026-04-08T21:30:00Z to 2026-04-08T23:00:00Z
핵심 로그 항목:
{
"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_commitcallback에서location_changed?→_location_field_changed?('longitude')호출 - 동일 시각에 2건 발생 — 동일 Facility에 대한 연속적인 업데이트이거나, 2개의 서로 다른 Facility 업데이트로 추정
추가 검색 — warn 레벨에서 latitude/longitude type 관련 경고 검색:
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?메서드에서before와after값을 읽은 후to_f변환을 추가해야 합니다.saved_changes['sys']에서 가져온 값이 String, Integer, Float 등 다양한 타입일 수 있으므로, 산술 연산 전에 안전하게 Float로 변환해야 합니다.to_f!대신to_f를 사용하여 변환 실패 시 0.0을 반환하도록 하는 것이 안전합니다 (이미 rescue 블록이 있지만, 예방적 방어가 더 적절합니다).
단기 개선 (1주 이내)#
sysJSONB 컬럼에 String으로 저장된 latitude/longitude 값이 있는 Facility 레코드를 조회하여 Float로 변환하는 데이터 마이그레이션을 실행해야 합니다. 이는 동일 에러의 재발을 방지합니다.saved_changes에서sys값을 읽는 다른 코드 경로가 있는지 확인하고, 유사한 타입 불일치 문제가 없는지 점검해야 합니다.
장기 개선 (재발 방지)#
sysJSONB 컬럼에 저장되는 latitude/longitude 값에 대해 데이터베이스 레벨의 타입 검증(CHECK constraint 또는 Rails validation)을 추가하여 String 값이 저장되는 것을 원천적으로 방지해야 합니다._location_field_changed?처럼saved_changes에서 raw 값을 읽는 패턴은 타입 안전성 문제가 있으므로, JSONB 값 비교를 위한 공통 유틸리티 메서드를 도입하는 것을 고려할 수 있습니다.
Monitoring#
- 동일 에러 재발 모니터링:
service:cupixworks-api status:error "Error occurred during comparing location"
- latitude/longitude type 경고 모니터링:
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로 제한됩니다.