JSON_CONTAINS(), IN 절의 검색 대상을 여러 개로 확장하는 전략
0. 글을 시작하며
현재 진행하는 프로젝트에는 해시태그 개념이 존재하고, 이 해시태그를 이용한 검색 기능을 제공하고 있었습니다. 기존에는 하나의 post는 하나의 해시태그만을 가질 수 있다는 정책을 설정하고 사용하고 있어서 데이터를 다음과 같은 형태로 저장하고 있었습니다.
post_id | post_name | post_content | ... | hash_tag |
1 | 석촌호수 벚꽃 | 석촌호수 벚꽃이 정말... | ... | 벚꽃 |
2 | 따뜻한 날씨 | 요즘 날씨가 많이 따뜻... | ... | 일상 |
... | ... | |||
104 | 날씨 좋은 날 나들이 | 이렇게 날씨 좋은 날 ... | ... | 나들이 |
105 | Querydsl 도입기 | 기존 프로젝트에서는... | ... | 기술 |
그래서 사용자가 "벚꽃" 또는 "나들이" 라는 해시태그가 포함된 글을 보고 싶다면 다음과 같은 쿼리를 사용해서 해당 태그를 포함한 포스트들을 조회해오도록 처리하고 있었습니다.
SELECT *
FROM post
WHERE post.hash_tag IN ('벚꽃','나들이');
하지만 갑자기 요구사항이 변경되면서 다음과 같은 중대한 변경사항이 발생하게 됩니다.
하나의 포스트는 여러 개의 해시태그를 가질 수 있다.
상당히 중대한 변경사항 이었습니다. 하나의 포스트가 N개의 해시태그를 가질 수 있는 형태로 요청사항이 변경되면서 이 내용을 DB 상에 어떻게 유지할 지, 검색은 어떻게 처리할 지 등 여러 가지 이슈가 발생하게 됩니다. 프로젝트 상황 상 DB Scheme을 쉽게 수정할 수 있는 상황이 아니다보니 여러 협의 과정을 거쳐서 하나의 글이 여러 개의 해시태그를 가지는 경우에는 다음과 같이 저장하도록 변경되었습니다.
post_id | post_name | post_content | ... | hash_tag |
1 | 석촌호수 벚꽃 | 석촌호수 벚꽃이 정말... | ... | 벚꽃,나들이 |
2 | 따뜻한 날씨 | 요즘 날씨가 많이 따뜻... | ... | 일상,기록 |
... | ... | |||
104 | 날씨 좋은 날 나들이 | 이렇게 날씨 좋은 날 ... | ... | 나들이,날씨,화창 |
105 | Querydsl 도입기 | 기존 프로젝트에서는... | ... | 기술,JPA,Querydsl |
즉, 여러 개의 해시태그들을 ' , ' 문자를 구분자로 사용해서 이어 붙여서 하나의 String 으로 저장하는 방식으로 데이터를 저장하게 되었습니다. 하지만 이렇게 저장하게 되면서 기존에 지원하던 "해시태그 기반 검색기능"을 수정하는 데 어려움에 직면하게 됩니다. 왜냐하면 하나의 포스트가 여러 개의 해시태그를 가질 수 있게 되면서 검색 기능이 지원해야하는 범위가 다음과 같이 확장 됩니다.
사용자가 "나들이", "벚꽃" 이라는 2개의 해시태그를 사용해서 검색하면 이 둘을 모두 포함하는 포스트만을 필터링해서 보여주고, 만약 "나들이" 만으로 검색하면 해시태그에 "나들이"를 포함하는 모든 포스트를 보여준다.
여기서 문제가 발생하게 됩니다. 우리가 MySQL에서 어떤 column의 값이 특정 값의 Array에 포함되는 지 여부를 확인하는 가장 간편한 방법은 IN절을 사용하는 것입니다. 예를 들어서 'post' 라는 이름을 가진 Table에 저장된 record 들 중에서 'hash_tag' 라는 column의 값이 ['tagA', 'tagB', 'tagC'] 라는 array에 포함되는 것만 필터링 해서 보고 싶다고 할 때 우리는 간편하게 다음과 같은 쿼리를 작성해 볼 수 있을 것 입니다.
SELECT *
FROM post
WHERE post.hash_tag IN ('tagA','tagB','tagC');
만약 post table에서 hash_tag 라는 column이 오직 하나의 tag만을 갖는다면 위의 쿼리로도 충분히 요구사항을 커버할 수 있습니다. 하지만 IN 절의 경우 array IN array 의 형태로 검색하는 것이 불가능하기 때문에 기존의 쿼리를 살짝 수정하는 것으로는 기존의 검색 기능을 그대로 지원할 수 없는 문제가 발생하게 됩니다.
그래서 이번 글을 통해서 이 문제를 어떻게 해결했는 지, 그리고 이 문제를 해결한 방식에 대한 저의 생각을 다뤄보고자 합니다.
1. JSON_CONTAINS(), 문제를 해결한 방법
https://www.mysqltutorial.org/mysql-json/mysql-json_contains/
결과적으로 제가 문제를 해결한 방법은 MySQL에서 지원하는 'json_contains' 함수를 사용해서 해결하였습니다. json_contains 함수는 다음과 같은 형식으로 사용할 수 있습니다.
JSON_CONTAINS(target, candidate); # 1 - true, 0 - false
이 함수는 json 배열 target이 json 배열 candidate을 포함하는 지를 확인해주는 함수 입니다. 그래서 candidate가 target에 포함되는 경우 1을, 아닌 경우 0을 반환하게 됩니다. 그래서 위에서 마주한 문제 상황을 JSON_CONTAINS() 함수를 사용해서 다음과 같이 쿼리를 작성해볼 수 있습니다.
SELECT *
FROM post
WHERE JSON_CONTAINS(CAST(CONCAT('["', REPLACE(post.hash_tag, ',', '","'), '"]') AS JSON),
CAST(CONCAT('["', REPLACE('벚꽃,나들이', ',', '","'), '"]') AS JSON)) = 1;
쿼리가 다소 IN 절에 비해서 직관적이지는 못합니다. 여기서 각 특정 구문에 대해서 조금 보충하자면 CAST ... AS JSON 구문은 'tagA,tagB' 의 형태로 구성된 문자열을 json 배열로 변환해주는 쿼리라고 이해하시면 됩니다. 애플리케이션 상에서 parameter를 매핑하는 방식에 따라서 해당 구문을 조금씩 수정해서 사용하시면 됩니다.
결과적으로 위와 같이 쿼리를 구성하면 여러 개의 해시태그를 가질 수 있는 각각의 포스트에 대해서 '벚꽃'과 '나들이'를 모두 태그로 가지는 포스트를 검색해서 가져올 수 있게 됩니다. JSON_CONTAINS(target, candidate) 함수 자체가 candidate가 target에 포함되는 JSON 인지 확인하는 역할이기 때문에 검색하는 태그의 순서에도 제약을 받지 않습니다. 즉, 위의 쿼리와 다음 쿼리가 서로 동일한 결과를 반환합니다.
SELECT *
FROM post
WHERE JSON_CONTAINS(CAST(CONCAT('["', REPLACE(post.hash_tag, ',', '","'), '"]') AS JSON),
CAST(CONCAT('["', REPLACE('나들이,벚꽃', ',', '","'), '"]') AS JSON)) = 1;
2. 글을 마무리 하며
제가 문제 해결에 사용한 이 방식은 현재 프로젝트에서 의도한 내용대로 잘 동작하고 있습니다. 하지만 많은 분들이 공감하실 만한 다음과 같은 문제점을 포함하고 있습니다.
- 쿼리가 기술하고자 하는 로직이 직관적이지 못하다.
- 인덱스를 사용할 수 없으므로 성능 문제가 발생할 가능성이 높아보인다.
- MySQL에 의존적인 코드가 되어 다양한 DBMS에 대응할 수 없는 로직이다.
저 또한 이 문제를 두고 많은 고민을 하였고, 현재 프로젝트는 사용할 DBMS를 교체할 계획이 없기에 이와 같은 방식을 채택하였고, 성능문제도 현재 데이터가 누적 1500건 정도 밖에 되지 않아서 성능문제가 발생하지 않고 있어 현재 방식을 유지하고 있습니다. 그래서 1번 문제의 경우 주석을 이용해서 직관적이지 못한 로직을 보완하고자 하였습니다.
하지만 여전히 이것이 최선의 해결책이라고 생각하지는 않습니다. 가장 이상적인 것은 검색에 특화된 구조로 DB 구조를 변경하는 것이겠지만 현재 프로젝트 특성 상 이것이 어려운 상황이어서 다음과 같은 절충안을 찾아서 적용하였습니다. 분명 이보다 더 직관적이고 더 효율적으로 처리할 수 있는 방법이 더 있을 것이라고 생각합니다. 혹시 이보다 더 직관적이고 효율적인 방법이 있다면 댓글로 나눠주시면 이 사례를 "이렇게 하지 마세요!"에 대한 예시로 삼아서 더 좋은 글이 될 수 있을 것 같습니다.
현재 저와 같은 문제로 고민하시는 상황인 분들이 계시다면 이 글이 조금이나마 도움이 되었으면 좋겠습니다.
감사합니다.
'Computer Science > 데이터 베이스' 카테고리의 다른 글
간단하게 살펴보는 빅데이터 분석과 관련 개념들 (1) | 2023.12.10 |
---|---|
[Google BigQuery] WITH문, 성능에 문제없을까? (0) | 2023.04.04 |
[Google BigQuery] 1라인 쿼리에서 변수를 사용하고 싶을 때 (0) | 2023.04.03 |
댓글
이 글 공유하기
다른 글
-
간단하게 살펴보는 빅데이터 분석과 관련 개념들
간단하게 살펴보는 빅데이터 분석과 관련 개념들
2023.12.10 -
[Google BigQuery] WITH문, 성능에 문제없을까?
[Google BigQuery] WITH문, 성능에 문제없을까?
2023.04.04 -
[Google BigQuery] 1라인 쿼리에서 변수를 사용하고 싶을 때
[Google BigQuery] 1라인 쿼리에서 변수를 사용하고 싶을 때
2023.04.03