해시 태그 테이블 설계

고민

게시글에 해시 태그 기능을 넣을 때 DB 스키마를 어떻게 설계할 지 고민해보았다.

게시글 테이블에 해시 태그 컬럼을 추가해서 해시 태그를 저장하는 방법과 해시 태그 테이블을 분리하여 게시글 아이디를 외래키로 사용하는 방법 2가지에 대해 고민해보았다.

 

해시 태그 방식 

해시 태그 테이블을 사용했을 때는 매번 조인을 해야하고, 그에 따라 쿼리가 좀 더 복잡해지며, 데이터가 많아지면 조인으로 인한 성능 저하가 발생할 수 있다는 단점과 정규화를 통해 중복을 최소화하고, 태그에 대한 검색에서는 성능이 잘 나올 수 있다는 장점이 있다.

 

컬럼 방식

컬럼 방식으로 저장 시 검색에서 parsing 또는 like 연산에 의한 성능 저하가 발생하고, 정규화가 힘들다는 단점이 있지만, 테이블 구조가 심플하고, 조인이 없어 간단하게 구현할 수 있다는 장점이 있다.

JSON vs TEXT

하나의 게시글이 여러 개의 해시 태그를 가질 수 있기 때문에 컬럼 방식을 사용할 경우 여러 개의 해시 태그를 하나의 컬럼에서 구분하여 저장할 수 있어야한다. 이때 저장 타입을 MySQL 5.7.8버전 이후부터 제공하는 JSON으로 할 것인지, TEXT로 할 것인지에 대해 고려해보아야 한다.

아래 레퍼런스의 글을 참고해보면 JSON 타입을 사용했을 때 Binary JSON 저장 포맷으로 변환 작업이 성능 저하로 이어지고, 데이터를 네트워크로 전송할 때도 Serialization 과정에서 JSON의 모든 필드에 대해 변환 작업이 필요하기 때문에 성능 저하로 이어지게 된다.

따라서 JSON 데이터의 여러 필드 중 특정 필드만 자주 사용되는 경우가 아니라면 TEXT 방식이 더 적합하다.

 

JPA로 JSON 타입 적용하기

처음에 JSON과 TEXT 타입의 차이를 고려하지 않고 JPA를 이용해서 JSON 타입으로 저장하는 코드를 작성해보았다.

AttributeConverter의 구현체를 구현하여 String을 Set으로 저장하도록 구현하였다. 

public class StringSetConverter implements AttributeConverter<Set<String>,String> {
    private static final ObjectMapper mapper = new ObjectMapper()
            .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
            .configure(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES, false);

    @Override
    public String convertToDatabaseColumn(Set<String> attribute) {
        try{
            return mapper.writeValueAsString(attribute);
        } catch (JsonProcessingException e) {
            throw new IllegalArgumentException();
        }
    }

    @Override
    public Set<String> convertToEntityAttribute(String dbData) {
        try {
            return mapper.readValue(dbData, Set.class);
        } catch (IOException e) {
            throw new IllegalArgumentException();
        }
    }
}


@Entity
public class Article {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @ManyToOne
    @JoinColumn(name = "user_id", referencedColumnName = "id")
    private UserAccount userAccount;

    @Column(nullable = false)
    private String title;
    @Column(nullable = false, length = 10000)
    private String content;
    @Convert(converter = StringSetConverter.class)
    private Set<String> hashTag;

    @Column(nullable = false)
    private LocalDateTime createdAt;
    @Column(nullable = false)
    private LocalDateTime modifiedAt;
}

 

결론

해시태그에 대해 추가적인 정보(생성 날짜, 조회수 등)가 필요하거나, 해시 태그를 특정할 수 있는 상황이라면 테이블을 사용하는 것이 더 적합하고, 단순히 검색 조건으로만 사용하고 사용자가 자유롭게 작성하는 형식이라면 컬럼 방식이 더 적합할 것이라고 정리했다.

 

Reference

MySQL JSON vs TEXT

'데이터베이스 > MySQL' 카테고리의 다른 글

정규화  (1) 2024.01.04
MySQL 아키텍처  (0) 2024.01.04
[MySQL] INSERT INTO SELECT  (0) 2023.05.09
[MySQL] workbench ERD 자동 생성  (0) 2023.04.15
[MySQL] 외래키 추가  (0) 2023.04.15