이 문제의 근원은 ActiveRecord 객체를 직렬화하는 코드에 있었습니다. 특히, attributes.merge(id: public_id)와 같은 코드가 원인이었습니다. 이 코드는 기본 키를 서비스 간 통신에 사용되는 공개 식별자로 대체하려는 의도였으나, attributes가 문자열 키를 가진 해시를 반환하고 여기에 심볼 키(:id)로 값을 병합하면서 동일한 id에 대해 문자열 키와 심볼 키가 공존하는 해시({"id" => 1, :id => "one"})를 생성했습니다.
Rails 버전별 동작 변화
-
Rails 7.2:
render json:호출 시 내부적으로as_json을 사용했습니다.as_json은 키를 정규화(심볼 키를 문자열로 변환)하고 중복을 제거하여, 최종 JSON 응답에는 항상 마지막에 제공된 값이 사용되어 중복 키 문제가 표면화되지 않았습니다. -
Rails 8.0: Rails 8의 성능 최적화 커밋은
render json:호출 시 템플릿 옵션이 없는 경우as_json호출을 건너뛰는 “고속 경로(fast path)”를 도입했습니다. 이 고속 경로는 키 정규화 및 중복 제거 로직을 우회하여, 혼합 키 해시({"id" => 1, :id => "one"})가 그대로 JSON 인코더에 전달되어{"id":1,"id":"one"}과 같이 중복 키가 포함된 JSON 응답이 생성되었습니다.
변경 로그의 혼란
ActiveSupport 7.1.3 변경 로그에는 “ActiveSupport::JSON.encode가 중복 키를 방지하도록 수정”이라는 내용이 있었으나, 이 수정 사항은 7.1.4에서 문서화되지 않은 채 롤백되어 혼란을 야기했습니다.
중복 키 방지 및 감지 전략
이러한 회귀를 방지하기 위한 몇 가지 방법이 있습니다.
-
요청 스펙(Request Specs): JSON 응답 본문의 정확한 형태를 검증하는 요청 스펙을 작성하면 중복 키가 즉시 불일치로 감지됩니다.
-
json젬 활용:json젬 2.14.0 버전부터는 동일한 키가 문자열과 심볼 형태로 동시에 존재할 경우 경고를 발생시키며, 3.0 버전에서는 기본적으로 오류를 발생시킬 예정입니다. -
ActiveSupport설정:config.active_support.disallowed_deprecation_warnings를 사용하여json젬의 경고를ActiveSupport의 감가상각 프레임워크로 전달하고,config.active_support.disallowed_deprecation = :raise를 설정하여 테스트 또는 개발 환경에서 이를 오류로 처리할 수 있습니다.
이러한 다층적인 접근 방식은 모든 응답 본문을 상세히 검증하지 못하더라도, 자동화된 테스트나 수동 테스트 과정에서 중복 키 문제를 조기에 발견하여 실패하도록 유도할 수 있는 가벼운 안전장치가 됩니다.