[모두의 플리] 외부 API 콘텐츠 수집 이슈 해결 기록

배경

외부 API 연동 기반과 Spring Batch 수집 작업을 구현한 뒤, 실제 화면과 DB 데이터를 확인하면서 몇 가지 문제가 드러났다.

초기 구현에서는 외부 API 응답을 Content로 변환하고 저장하는 구조에 집중했다.

하지만 실제 데이터가 들어오자 다음 문제가 확인되었다.

  • TMDB 장르 태그가 genre:35처럼 내부 ID 형태로 노출됨
  • 스포츠 콘텐츠 썸네일이 null로 저장되어 잘못된 placeholder 요청이 발생함
  • 코드 수정과 기존 DB 데이터 보정이 서로 다른 문제라는 점이 드러남

이번 글은 각 이슈가 왜 발생했는지, 어떻게 수정했는지, 그리고 어떤 후속 처리가 필요한지를 정리한 기록이다.

이슈 1. TMDB 장르 태그가 genre:35처럼 노출됨

문제 상황

TMDB 콘텐츠를 수집한 뒤 화면에서 태그가 다음처럼 보였다.

genre:35
genre:18

사용자 입장에서는 이 값이 어떤 장르인지 알 수 없다.

태그는 검색과 필터링에도 사용될 수 있기 때문에, 내부 식별자가 그대로 노출되는 것은 적절하지 않았다.

원인

초기 구현에서는 TMDB 응답의 genre id를 사용자에게 보여줄 장르명으로 변환하지 않고 태그 후보로 사용했다.

즉, 외부 API의 내부 식별자를 서비스 표시 값으로 그대로 넘긴 것이 문제였다.

해결 방법

TMDB genre id를 장르명으로 변환하는 매핑을 추가했다.

예를 들면 다음과 같다.

35 -> 코미디
18 -> 드라마

변환 후 Content 태그에는 genre:35가 아니라 코미디, 드라마 같은 표시 가능한 값이 저장되도록 했다.

남은 주의점

이미 DB에 저장된 기존 태그는 코드 수정만으로 자동 변경되지 않는다.

기존 데이터를 바꾸려면 다음 중 하나가 필요하다.

1. DB 초기화 후 재수집
2. 별도 보정 SQL 실행
3. 별도 migration 작성

이슈 2. 스포츠 콘텐츠 썸네일이 null로 저장됨

문제 상황

스포츠 콘텐츠 화면에서 이미지가 정상적으로 나오지 않고, 프론트에서 /placeholder-movie.png를 요청하는 상황이 확인되었다.

문제는 스포츠 콘텐츠인데도 movie placeholder가 요청된다는 점이었다.

원인

백엔드에서 스포츠 콘텐츠의 thumbnailUrl이 null로 내려가고 있었다.

TheSportsDB 응답을 확인해보니 경기마다 이미지 필드가 항상 동일하게 채워지는 것이 아니었다.

초기 구현에서는 대표 이미지 후보를 제한적으로만 확인했다.

하지만 실제 응답에서는 strThumb, strPoster가 비어 있고, 팀 배지나 리그 배지 같은 다른 이미지 필드만 존재하는 경우가 있었다.

해결 방법

스포츠 콘텐츠 썸네일 후보를 확장했다.

strThumb
strPoster
strBanner
strSquare
strFanart
strHomeTeamBadge
strAwayTeamBadge
strLeagueBadge

이제 대표 이미지가 없어도 팀 배지나 리그 배지를 썸네일 후보로 사용할 수 있다.

남은 주의점

모든 이미지 후보가 비어 있으면 여전히 thumbnailUrl은 null일 수 있다.

또한 기존 DB에 이미 null로 저장된 스포츠 콘텐츠는 코드 수정만으로 자동 갱신되지 않는다.

현재 외부 콘텐츠 upsert 정책은 기존 콘텐츠를 다시 만나면 lastSyncedAt만 갱신하기 때문이다.

이슈 3. 코드 수정과 기존 DB 데이터 반영은 다르다

문제 상황

코드는 수정했지만, 화면에서는 여전히 기존 문제가 남아 있는 것처럼 보일 수 있었다.

예를 들어 다음과 같은 상황이다.

코드 수정 완료
하지만 DB에는 기존 genre:35 태그가 남아 있음

코드 수정 완료
하지만 DB에는 기존 thumbnailUrl = null 데이터가 남아 있음

원인

코드 수정은 앞으로 새로 저장되는 데이터나 재수집 정책에만 영향을 준다.

이미 DB에 저장된 row는 별도로 갱신하지 않는 한 그대로 남아 있다.

특히 현재 외부 콘텐츠 upsert 정책은 안전하게 동작하기 위해 기존 콘텐츠의 핵심 필드를 덮어쓰지 않는다.

기존 콘텐츠 재수집 시:
- title 유지
- description 유지
- thumbnailUrl 유지
- tags 유지
- lastSyncedAt만 갱신

이 정책은 외부 API 값이 기존 데이터를 마음대로 덮어쓰지 않게 해주는 장점이 있다.

하지만 이번처럼 기존 데이터 자체를 보정해야 하는 경우에는 별도 조치가 필요하다.

해결 방향

기존 데이터를 반영하려면 다음 중 하나를 선택해야 한다.

1. DB 초기화 후 재수집
2. 보정 SQL 실행
3. 별도 migration 작성

개발 초기 단계라면 DB 초기화 후 재수집이 가장 단순하다.

하지만 이미 운영 중인 DB라면 V1 초기 스키마를 수정하는 방식은 위험하다.

그 경우에는 별도 migration으로 변경 사항을 적용해야 한다.

정리

이번 이슈들은 단순 코드 버그라기보다 외부 데이터를 서비스 데이터로 바꾸는 과정에서 생긴 문제였다.

외부 API 값은 그대로 저장할 수 있다고 해서 항상 그대로 보여줘도 되는 값은 아니다.

또 코드가 수정되었다고 해서 기존 DB 데이터까지 자동으로 바뀌는 것도 아니다.

이번 작업을 통해 외부 API 수집 기능에서는 다음을 항상 분리해서 봐야 한다는 점을 배웠다.

1. 외부 API 원본 값
2. 서비스 내부 저장 값
3. 사용자에게 보여줄 표시 값
4. 기존 DB 데이터 보정 여부

이 구분을 하지 않으면 코드는 수정됐는데 화면은 그대로인 것처럼 보이거나, 반대로 화면 문제를 프론트 문제로 오해할 수 있다.