얼쑤의 곡 목록 API를 Swagger UI에서 열었더니 Could not render responses가 표시됐습니다. 브라우저 콘솔에는 Maximum call stack size exceeded가 남았습니다. 응답을 살펴보니 111곡을 담은 JSON이 약 1.76 MB였고, 그중 약 99%가 곡마다 붙은 score였습니다. 곡 하나를 고르는 화면에서 111곡의 악보까지 받고 있었습니다.

목록에서 악보 본문 분리

곡 하나당 메타데이터는 평균 189바이트였고, 악보는 평균 약 16 KB였습니다. 악보가 들어간 응답 타입을 목록과 상세에서 함께 쓴 결과였습니다. 곡이 늘면 각 곡의 악보 크기만큼 목록 응답도 함께 커지는 구조였습니다.

저는 목록 항목을 SongListItem으로 분리했습니다. 목록의 result.items에는 제목, 악기, 난이도, 기본 BPM처럼 곡을 고르는 데 쓰는 필드와 악보 버전 등의 메타데이터를 담았습니다. 이때 백엔드의 목록 변환에서도 scoreJson 파싱을 뺐습니다. 이후 목록에 장단명을 복구하면서 서버는 그 이름을 얻기 위해 원본을 다시 파싱하게 됐습니다. 악보에서 읽은 장단명은 목록의 별도 필드로 보내고, score 본문은 곡 단위 조회에서 제공합니다.

이미 앱이 사용하는 응답에서 필드를 빼는 변경이어서 소비 코드를 먼저 확인했습니다. 앱의 역직렬화 모델에서 score는 기본값이 null인 선택 필드였고, 상세 화면은 곡별 악보를 별도로 조회하고 있었습니다. 실제 HTTP와 MySQL을 연결한 통합 테스트에서 목록 응답은 scoreVersion을 제공했고 score 필드는 빠졌습니다. 같은 곡의 악보 API는 HTTP 200을 반환했고 응답의 악보 JSON은 DB에서 읽은 원본과 일치했습니다.

111곡의 메타데이터 평균에 곡 수를 곱하면 약 21 KB입니다. 이 값은 필드를 분리했을 때 목록이 어느 정도 작아질지 계산한 추정치입니다.

프록시에서 JSON 압축

응답에서 어떤 필드를 보낼지 정한 뒤, 전송량은 따로 살펴봤습니다. 브라우저에서 남긴 별도의 압축 비교 기록에는 당시 곡 목록이 약 1.8 MB에서 129 KB로, 가장 큰 곡 응답은 98 KB에서 8.3 KB로 줄어든 값이 있었습니다. gzip이 줄인 것은 전송할 바이트입니다. 클라이언트가 압축을 풀면 JSON 구조와 필드가 복원되므로, 목록에서 악보를 빼는 일과는 다른 층에서 효과가 납니다.

Nginx에서는 /api/의 application/json을 압축 대상으로 지정하고, Content-Length를 기준으로 최소 길이를 1,024바이트로 설정했습니다. 클라이언트가 Accept-Encoding: gzip을 보낸 경우에 gzip 응답을 받을 수 있게 하고, Vary: Accept-Encoding으로 캐시가 압축본과 원본을 구분할 수 있게 했습니다.

가짜 백엔드와 실제 Nginx를 Docker에서 함께 띄워 응답 헤더를 확인했습니다. 큰 JSON은 gzip을 요청했을 때 Content-Encoding: gzip이 붙었습니다. identity로 요청한 같은 JSON과 짧은 JSON, 오디오 응답은 압축 없이 받았습니다.

운영 응답의 헤더와 크기 확인

저장소에 설정을 병합한 뒤에도 운영 응답에는 gzip이 없었습니다. 컨테이너가 별도의 운영 설정 파일을 읽고 있어 그 파일에 압축 옵션을 반영했습니다. 컨테이너가 읽는 파일을 찾아 변경을 적용한 절차는 별도 글에 정리했습니다. 파일 교체 뒤에도 남은 컨테이너 설정

운영에서 비교한 요청관측 결과
곡 목록, gzip 요청 없음원본 19,827바이트, Vary: Accept-Encoding
곡 목록, gzip 요청2,206바이트, Content-Encoding: gzip, Vary: Accept-Encoding
약관, gzip 요청원본 896바이트

표의 목록 크기 두 값은 악보를 분리한 뒤 같은 운영 목록에서 압축 요청 여부만 바꿔 잰 값입니다. 처음 확인한 약 1.76 MB는 다른 시점의 목록에서 측정했습니다. 수정 뒤에는 곡 목록과 상세의 HTTP 응답과 운영 전송량을 확인했습니다. Swagger UI의 표시 결과는 검증 기록에 남아 있지 않습니다.