[PostgreSQL] 데이터를 일관되게 전달해 보자

데이터를 일관되게 전달하도록 ORM을 제거하고 쿼리를 최적화해 보자

 문제

1편의 문제를 해결한 이후 새로운 이슈가 발생했다. 해당 이슈는 게시글 리스트의 댓글 개수와 실제 게시글에 들어갔을 때 댓글 개수가 달라요 이다. 이전에 수정한 코드가 문제가 있던 것은 아니고, 기존의 로직 자체의 문제였다.

게시글 리스트(기글하드웨어) - 해당 이미지는 예시입니다.

위 이미지의 델 oem 4090의 모험(완) 게시글을 보면 댓글이 6개라고 나와 있다. 그런데, 실제로 해당 게시글을 클릭해서 들어가면 5개, 4개 같이 댓글 개수가 줄어드는 버그가 있었다.

이는 서비스의 댓글 정책에서 기인하는 문제이다. 해당 정책은 댓글의 노출이 필요한 조건이 복잡하다.

그래서 그랬을까? 애초에 기존 로직이 조금 이상했었다. 게시글 리스트를 가져올 때는 단순하게 삭제된 댓글을 제외한 개수를 가져와서 반환하고, 게시글 상세 정보 조회 시에는 실제로 댓글 리스트에 정책을 js 코드로 필터를 작성해서 반환했다. 그래서 댓글의 작성과 삭제가 빈번하게 일어나는 게시글일수록 점점 차이가 심하게 나기 시작했다.

솔직히 처음 코드를 보고 조금 벙쪘다. 아무리 그래도 그렇지… 두개의 댓글 개수 카운팅 방식이 같지 않다니…

문제를 어떻게 해결할 것인가

문제의 원인을 파악하고 나서, 어떻게 해결할 지 고민이 많이 되었다. 곰곰히 생각하면서 세가지의 생각이 떠올랐다.

  1. 프론트엔드에게 처리 맞기기
    • 절대 안된다.
    • 프론트엔드에게 처리를 맞기면 똑같은 문제가 발생할 것이다.
  2. 게시글 목록 조회시 댓글도 싹다 불러와서 정책 필터 적용시키기
    • 전체 게시글 개수(n) * 전체 댓글 개수(m) = O(n*m) 만큼의 시간 소모
    • 뭔가 딱봐도 시간이 오래 걸릴 것 같은 느낌
  3. orm을 제거하고 정책을 sql 쿼리에 녹여서 만들기
    • 생각만해도 어렵고, 귀찮다.
    • 근데 이 방법 밖에 없다.

결론은 sql 쿼리를 사용하는 것이었다. 하지만 여기에도 문제가 존재한다.

추가 문제

  • 기존 response dto를 유지해야함.
    • 이것이 제일 큰 문제이다.
    • orm은 join시 자동으로 아래 예시 처럼 nest object를 만들어준다.
    • 기존 response dto는 orm의 nest object에 강하게 의존하고 있다.
    • sql로도 해당 nest object를 만들 수 있어야 한다.

일반적으로 sql로 내려받은 데이터는 object안에 object가 없는 평면적인 데이터이다. 이를 코드상으로 해결하기에는 꽤나 보기싫은 코드가 생겨나게 될 것이다.(많은 loop와 map을 이용한 코드)

상당히 보기 지저분한 코드가 나올 것이라고 예상할 수 있다. 또한 2중 중첩이 아니라. 3중,4중으로 가게되면 더욱 복잡해질 것이다.

답은 postgresql의 jsonb 기능이다. postgresql은 groub by 집계함수로 json을 만들어 낼 수가 있다. 이를 통해 결론적으로 nested object를 만들어 바로 반환할 것이다.

postgresql에는 jsonb와 json 타입이 있다. jsonb는 바이너리, json은 텍스트이다. 바이너리로 구성된 jsonb는 내부 값 컨트롤(추가, 수정, 삭제)이 가능하다.

해결 과정

with절 적극 활용하기

postgresql에는 with절이라는 것이 있다. with절은 특정 select 구문을 한번 실행하고 임시로 저장해두는 역할을 한다. 비슷한 쿼리를 여러번 실행해야 하는 경우 속도 측면에서 엄청난 강점을 가진다. 이를 보통 CTE(common table expression)이라고 한다. (참고 링크)

대상 게시글 선택

with절을 활용해 게시글을 조회한다. 해당 게시글 id로 댓글 조회 row 개수를 최소화 하는 목적을 가진다.

댓글 선택

댓글 테이블의 self join을 통해 정책을 sql에 녹여낸다. self join시 row 개수가 많으면 시간이 오래 걸리므로 위의 게시글 조회를 저장해둔 결과를 가져와 댓글 범위를 최소화 한다. 이후 댓글과 유저를 join 한 후 유저 정보를 jsonb로 변환한다. 해당 결과도 with절로 저장해둔다.

게시글과 댓글 조합 후 json 제작

대상 게시글과 댓글 정보를 이용해 join을 맺는다. 이를 통해 게시글-댓글리스트를 json으로 만들고 전달할 수 있게 된다.

후기

해당 쿼리를 게시글 리스트 조회 API와 게시글 상세 조회 API 모두에 사용했다. 같은 쿼리를 사용함으로써 두 API의 데이터를 일관적으로 전달할 수 있었다. 또한 앞서 언급한 jsonb를 이용해서 response dto도 그대로 유지할 수 있었다.

의외로 추가적인 소득도 있었다. 속도가 1s에서 200ms 대로 줄었던 것이다. with절을 이용한 방식이 효과가 컸던 것 같다. 이번 문제를 해결하기 위해 처음으로 사용해본 것인데, 앞으로도 애용할 것 같다.