본문 바로가기
🛠 백엔드/Spring

북마크 구현에 관한 고민

by meteorfish 2025. 3. 13.
728x90

현재 프로젝트에선 기업, 로드맵의 각 단계, 강의에 대한 북마크 기능을 지원하려고 한다. 북마크라는 테이블을 만들어 사용자가 북마크한 정보들을 저장하려고 하는데 어떤 식으로 저장하는 것이 합리적일까? 일단 내가 생각한 구현 방법은 2가지 이다.

 

1. 북마크 테이블을 만들고 기업, 로드맵, 강의 테이블가 각각 북마크 테이블과 연관관계를 가지도록 구현한다.

2. 북마크 테이블의 칼럼을 JSON으로 만들어 한 번에 저장한다.

 

1. JOIN vs. JSON type

 

먼저, 북마크 조회 시 JOIN을 3개 사용하는 경우와 JSON을 통째로 저장하여 사용하는 경우 성능적으로 얼마나 차이가 나는지 검증해보도록 하자.

 

 기업, 강의 영상, 로드맵 각 10,000개의 데이터가 있는 상황에서 업데이트는 한 번에 하나의 데이터만 변경되기 때문에, 3개의 JOIN과 JSON 타입의 성능 차이가 크게 두드러지지 않았다. 3개의 JOIN을 이용하여 북마크를 조회하는 경우 51분이라는 매우 긴 시간이 소요되었다. 반대로 JSON을 이용한 조회의 경우 70ms로 조회에 매우 적은 시간이 소요된다.

 JOIN 비용은 정말 무시하지 못할 정도로 크다는 것을 실감할 수 있는 결과였다. 당연스럽게 조회가 핵심 기능은 본 서비스에선 JSON 타입을 이용하여 구현하는 것이 타당하다고 판단했다.

 

2. LONGTEXT vs. JSON type

 

MySQL은 native JSON을 지원한다. MySQL에서의 공식 문서에선 LONGTEXT와 JSON 타입이 같다고 말한다. 둘 다 같은 최대 용량을 제공하기 때문에 어떠한 차이점이 있을지 궁금하였다. 두 타입의 차이점을 정리하면 다음과 같다.

  • MySQL JSON 타입은 Binary JSON으로 데이터를 저장 (구조화됨)
    • 조회를 빠르게 하기 위한 이진수 인코딩 + 메타데이터 + 딕셔너리 등 포함
  • JSON 타입은 내부적으로 LONGTEXT로 저장되기 때문에, 크기 제한은 4GB로 동일하다.
    • 그러나 내부의 파싱 엔진을 통해 삽입 후, 바로 이진 포맷으로 저장되어 별도의 처리없이 저장된다.
    • 반대로 Binary JSON을 JSON으로 파싱하는 과정이 있으므로 조회 시, Text가 더 빠르다.
  • JSON 타입의 validation 기능 제공
    • DB 저장 전 JSON 형태가 맞는지 확인이 가능하다.
  • JSON 값을 사용하여 Index 생성 가능
    • 가상 칼럼을 통해 JSON 데이터의 특정 경로에 인덱스 지정 가능하다.
  • JSON Function 제공

 

이렇듯 JSON 타입을 통해 데이터의 조회와 삽입 시 다양한 기능들을 제공한다. 그렇다면 성능적으로 LONGTEXT와 JSON의 차이가 있을까?

 

LONGTEXT

# 10,000개의 데이터 삽입
BookmarkDataLoader.loadBookmark(..) executed in 1266210ms

# 모든 데이터 조회
BookmarkController.getBookmark(..) executed in 88ms

 

JSON

# 10,000개의 데이터 삽입
BookmarkDataLoader.loadBookmark(..) executed in 1050871ms

# 모든 데이터 조회
BookmarkController.getBookmark(..) executed in 114ms

 

약 10,000 건의 데이터를 기준으로 북마크의 삽입 및 조회 과정을 계산해보았다. 데이터 삽입의 경우 JSON이 200000ms 정도 빠른 것을 확인할 수 있었다. 조회도 반대로 LONGTEXT가 26ms 정도 빠른 것을 확인할 수 있다. 본 서비스에선 데이터의 조회가 무엇보다 중요하다. 북마크 추가 기능의 경우, 한 번의 하나의 데이터만 저장하도록 구현하였기 때문에 사실상 대량의 데이터를 추가하는 경우가 매우 드물다. 조회의 경우 성능상 크게 차이가 존재하지 않는다. 그러나 JSON의 경우, LONGTEXT와는 다르게 Validation을 제공하기 때문에 JSON 타입을 사용하여 데이터 검증을 하는 것이 더 좋은 선택지라고 생각되어 JSON 타입을 사용하기로 결정했다.

 

3. MongoDB vs. MySQL JSON type

MongoDB는 JSON 형식의 Document를 사용하여 데이터를 저장한다. 이를 이번 서비스에 적용하면 성능적으로 장점을 가질 수 있을까?

 

MySQL JSON 타입

# 10,000개의 데이터 삽입
BookmarkDataLoader.loadBookmark(..) executed in 1050871ms

# 모든 데이터 조회
BookmarkController.getBookmark(..) executed in 114ms

# 1개의 데이터 업데이트
BookmarkController.addBookmark(..) executed in 172ms

 

MongoDB

# 10,000개의 데이터 삽입
BookmarkDataLoader.loadBookmark(..) executed in 441ms

# 모든 데이터 조회
BookmarkMongoController.getBookmark() executed in 247ms

# 업데이트
BookmarkMongoController.addBookmark(..) executed in 790ms

 

 

결과적으로 대량의 데이터 삽입 시, MongoDB가 압도적으로 빠른 것을 확인할 수 있다. 아까도 말했듯이 이 프로젝트에선 조회와 업데이트가 핵심기능이다. 모든 데이터 조회의 경우 약 2배 정도 MongoDB가 느린 것을 확인할 수 있고, 업데이트의 경우에는 약 7배까지나 느린 것을 알 수 있다. 이렇듯 MongoDB는 데이터 삽입에서만 강점을 가지기 때문에 이번 프로젝트에 적용하기에는 크게 메리트가 없어 보인다.

 

 이런 성능적인 면 외에도 우려되는 부분은 데이터의 일관성이다. 만약 유저가 탈퇴한다고 가정해보자. MySQL에선 유저에 대한 튜플을 삭제하면, 해당 유저에 대한 MongoDB의 북마크 또한 삭제해야 한다. 만약 북마크가 삭제되지 않은 상태에서 ID가 같은 유저가 회원가입을 하게 되면, 기존에 해당 ID를 사용하던 유저의 북마크 정보가 조회되는 문제가 발생할 수도 있다. 물론 이를 해결하는 방법은 존재할 수 있지만, 규모가 크지 않은 프로젝트에서 데이터베이스를 분리하면서 사용하는데에 메리트가 있을지는 잘 모르겠다.

 

결론

 MySQL 내에서 JSON 타입을 사용하여 북마크를 구현하기로 결정했다. MongoDB의 큰 장점은 Scale Out이라고 생각한다. 그러나 현재 RDB에서 각 칼럼별로 JSON을 하나의 String으로 저장하기 때문에, 다른 속성을 추가할 때 크게 문제가 되지 않아 보인다.

 LONGTEXT의 경우도, JSON 타입과의 성능적으로 큰 차이가 보이지 않는다. 오히려 JSON 타입에서 제공하는 Validation과 여러 Function들이 강점으로 다가왔다.

728x90