트러블슈팅

정규화 vs 반정규화

삼록이 2025. 7. 19. 00:06

혼자서 개인 프로젝트를 진행하다가 고민이 생겼다.

내가 한 DB설계에서 유저->직책->필드 연관관계가 맺어졌는데,

화면에 이 유저가 어떤 직책에서 어떤 필드로 일하는지에 대한 정보가 노출되는 곳이 많다.

게시물 목록에도, 게시물에도, 댓글에도, 댓글 목록에도 등등...

 

그러다보니,

유저에 직책, 필드까지 연관관계에 있다보니 이를 위해 쿼리가 여러 곳에 나가게 된다.

이를 FetchJoin을 통해 해결했지만 대신, join이 2번이나 더 추가되는 문제가 발생한다.

게시물이나 댓글 조회시 QueryDSL과 fetch join으로 쿼리를 한번만 나갈 수 있도록 해결했지만 2개의 join이 추가되었다.

 

그래서 차라리 User 엔티티에 String타입의 직책과필드명을 가진 컬럼을 하나 두면

굳이 직책과 필드까지 join할 필요없이 유저 객체에서 조회할 수 있겠다는 생각이 들었다.


하지만 그렇게 되면 이행적 종속된 속성을 테이블 안에 함께 저장하게 되어 제 3정규화를 위반한다

A(유저)->B(직책)->C(필드) 현재 이 관계인데 유저 엔티티에 필드가 들어가게 되니 데이터 중복이 발생하는 것이다.

그렇다면 데이터의 무결성 유지를 위해 정규화된 현재 구조를 유지할 것이냐?

성능을 위해 반정규화를 일부러 택할 것이냐? 

고민하게 되었다.

 

내가 만들려는 서비스에서 직책과 필드명(ex.드라마 프로듀서 /  유튜브 편집자)는 자주 조회되는 조합값이다.

그리고 서비스가 커진다고 것까지 미리 고려해둔다면, 반정규화가 성능상의 이점이 확실히 있다.

그리고 필드와 직책이 변경될 시 동기화 된 로직만 잘 짜둔다면 크게 문제가 없겠다 라는 판단을 내렸다.


기존에는 user.getJobRole.returnFiledAndName()에서 JobRole과 연관된 엔티티이니 getJobRole에서 쿼리 한번 나가고 또, returnFiledAndName()메서드는 JobRole과 연관된 field를 불러와서 여기서 또 쿼리가 한 번 더 나가서.

2개의 fetchJoin을 더 해서 쿼리가 나가는걸 막았었다.

그러나  User에 String 타입의 직책필드명 컬럼을 두는 것으로 바꾸고 아래처럼 리턴하는 DTO를 조립할 때  user에서 바로 꺼내 올 수 있도록 수정했다. 

  public CommentListResDto toListDtoFromEntity(){
        return CommentListResDto.builder()
                .content(this.content)
                .writerName(this.user.getName())
                .fieldAndJobRole(this.user.getJobRole().returnFieldAndName()) //기존
                .createdTime(this.getCreatedTime())
                .build();
    }
--------------------------------------------------------------------------------------

public CommentListResDto toListDtoFromEntity(){
    return CommentListResDto.builder()
            .content(this.content)
            .writerName(this.user.getName())
            .fieldAndJobRole(this.user.getFieldJob()) //수정
            .createdTime(this.getCreatedTime())
            .build();
}

따라서, 기존 4번의 조인에서 2번의 조인으로 수정해도

이렇게 쿼리 한번으로  아래와 같이 댓글을 불러 올 수 있게 되었다.