본문 바로가기

Project/DB

[Independe] 추천, 신고, 즐겨찾기 DB 설계에 대한 고민

자취생 커뮤니티 개발을 하며 게시글, 댓글에 대해 추천, 신고, 즐겨찾기와 같은 요구사항이 발생했다. 처음엔 해당 기능들은 다 게시글과 1 : N 관계로 구축하면 되겠다고 쉽게 생각했다. 하지만 1 : N으로 맵핑한 뒤 구현을 할 수록 이상함이 느껴졌다. 추천은 '회원이 게시글을 추천한다' 그렇다면 추천이라는 테이블엔 회원, 게시글에 대한 정보가 모두 존재해야 한다. 또한, 한명의 회원은 여러개의 게시글에 추천을 할 수 있고 하나의 게시글 또한 여러명의 회원에게 추천을 받을 수 있다. 즉 1 : N이 아닌 N : M 다대다 관계로 구축해야 했다.

 

이 다음 발생한 고민이 추천, 신고, 즐겨찾기는 결국 boolean 값으로 isRecommend, isReport, isFavorite과 같이 컬럼에 저장되는데 하나의 테이블에 다 밀어넣으면 안되나? 라는 생각을 했었다. 하지만 이 역시 좋은 생각은 아닌 것 같았다. 그 이유는

첫 번째로 테이블에 null 값이 너무 많을 것 같았다. 하나의 회원이 특정 게시글을 추천만 한 경우 isRecommend는 true이겠지만 나머지 컬럼은 모두 null이다. 물론 추천을 누를 때 나머지 컬럼이 null인지 체크한 뒤 false를 넣어줄 수도 있겠지만 좋은 방법은 아닌 것 같았다.

 

두 번째로 성능에 대한 문제였다. 물론 내가 개발한 프로젝트가 db에 엄청난 부하를 줄 만큼의 사용자나 데이터는 존재하지 않지만 추천, 신고, 즐겨찾기를 하나의 테이블에 모두 넣은 다음 회원이 즐겨찾기한 게시글을 조회하는 것 보다 즐겨찾기 테이블을 따로 만든 뒤 해당 테이블만 조회하는 것이 훨씬 성능상 이점이 클 것이라 판단했다.

 

세 번째로 확장성이다. 지금은 별다른 요구사항이 없어 isRecommend와 같은 boolean 값만 테이블에 저장하면 되지만 만약 신고의 경우 신고 횟수도 저장해줘 라는 요구사항이 발생했을 때 하나의 테이블에 모든 정보를 저장한다면 추천, 즐겨찾기는 자신의 기능과 상관없는 신고 횟수를 저장해야 한다. 이 역시 설계상 좋지 않다고 판단했다. 이러한 이유로 난 게시글 추천, 게시글 신고, 게시글 즐겨찾기, 댓글 추천과 같은 테이블을 따로 구축하기로 했다. 아래는 JPA를 이용한 코드이다.

 

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class RecommendPost extends BaseManyToManyEntity{

    @Id
    @GeneratedValue
    @Column(name = "recommend_post_id")
    private Long id;

    @ManyToOne(fetch = LAZY)
    @JoinColumn(name = "member_id")
    private Member member;

    @ManyToOne(fetch = LAZY)
    @JoinColumn(name = "post_id")
    private Post post;

    private Boolean isRecommend;

    @Builder
    public RecommendPost(Member member, Post post, Boolean isRecommend) {
        this.member = member;
        addPost(post);
        this.isRecommend = isRecommend;
    }

    public void updateIsRecommend(Boolean isRecommend) {
        this.isRecommend = isRecommend;
    }

    // 연관관계 메서드
    public void addPost(Post post) {
        this.post = post;
        post.getRecommendPosts().add(this);
    }
}
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class FavoritePost extends BaseManyToManyEntity {

    @Id @GeneratedValue
    @Column(name = "favorite_post_id")
    private Long id;

    @ManyToOne(fetch = LAZY)
    @JoinColumn(name = "member_id")
    private Member member;

    @ManyToOne(fetch = LAZY)
    @JoinColumn(name = "post_id")
    private Post post;

    private Boolean isFavorite;

    @Builder
    public FavoritePost(Member member, Post post, Boolean isFavorite) {
        this.member = member;
        this.post = post;
        this.isFavorite = isFavorite;
    }

    public void updateIsFavorite(Boolean favorite) {
        isFavorite = favorite;
    }
}
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class ReportPost extends BaseManyToManyEntity{

    @Id
    @GeneratedValue
    @Column(name = "report_post_id")
    private Long id;

    @ManyToOne(fetch = LAZY)
    @JoinColumn(name = "member_id")
    private Member member;

    @ManyToOne(fetch = LAZY)
    @JoinColumn(name = "post_id")
    private Post post;

    private Boolean isReport;

    @Builder
    public ReportPost(Member member, Post post, Boolean isReport) {
        this.member = member;
        this.post = post;
        this.isReport = isReport;
    }

    public void updateIsReport(Boolean isReport) {
        this.isReport = isReport;
    }
}

게시글 추천, 즐겨찾기, 신고는 회원, 게시글 모두 1 : N 다대일 관계로 맵핑하여 다대다 관계를 만들었다. 또한 난 setter를 사용하는 것 보다 update와 같은 이름을 사용해 메서드 사용 시 조금이라도 더 직관적으로 보일 수 있게 하였다.

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class RecommendComment {

    @Id
    @GeneratedValue
    @Column(name = "recommend_comment_id")
    private Long id;

    @ManyToOne(fetch = LAZY)
    @JoinColumn(name = "member_id")
    private Member member;

    @ManyToOne(fetch = LAZY)
    @JoinColumn(name = "comment_id")
    private Comment comment;

    private Boolean isRecommend;

    @Builder
    public RecommendComment(Member member, Comment comment, Boolean isRecommend) {
        this.member = member;
        this.comment = comment;
        this.isRecommend = isRecommend;
    }

    public void updateIsRecommend(Boolean isRecommend) {
        this.isRecommend = isRecommend;
    }
}

댓글 추천도 게시글과 동일한 방식으로 구현하였다. 이후 내 설계가 옳은지 조금 더 알아보기 위해 구글링을 해보았는데 많은 사람들이 하나의 테이블에 몰아 넣기보단 여러 테이블에 나누어 저장하는 방식을 추천하는것 같아 내 생각이 맞는듯 했다.

'Project > DB' 카테고리의 다른 글

Redis / RedisTemplate 실험  (0) 2024.01.20
[Independe] 채팅 DB 설계에 대한 고민  (2) 2023.12.05
[Independe] 대댓글 설계에 대한 고민  (1) 2023.09.14