⌨️ 연관 게시물 구현
그동안 막혀 있던 혈이 하나 뚫린 듯한 기분이 드는 프로젝트였다.
이전 과제로 한번 받았던 내용인데 그때도 어찌어찌 완수는 했지만 완성도가 너무 낮았고, 이 프로젝트를 하기 전까지 해야지 해야지 하면서 미루고 있었다.
이번 프로젝트를 하며 마침 기능을 붙힐 수 있다는 생각에 도전해 봤는데 좋은 결과가 나왔고, 개인적으로도 큰 성취감이 드는 기능 중 하나이다.
그리고 조금은 개발자라는 것에 대해 다시금 생각해보는 계기가 된 기능이다.
1. 연관에 대하여
연관이란 개념이 굉장히 모호 했다. 연관의 기준은? 무엇이 연관 게시물일까?
나는 아직 역량이 달려 이 부분에서 매우 힘들었다. 그동안 명확한 개념들에 대해서는 구현이 가능했지만, 이런 모호한 부분에 대해 기능을 구현하려니 사고가 마비되었다.
앞으로의 포스팅은 이러한 문제를 극복하고 또 개선해 나가는 나의 과정을 최대한 담아보는 내용이 될 것이다.
1.1. 시작은 주어진 명세부터
처음 시작은 과제 전형을 진행하며 받은 명세였다. 자세한 명세를 기술하긴 그렇고 몇몇의 로직만 말해보자면
- 전체 게시물의 60% 이상 등장 하는 단어는 연관도 계산에서 제외한다.
- 전체 게시물의 40% 이상 등장하는 단어가 2개 이상 나타나는 게시물을 서로 연관 게시물로 매핑한다.
- 그러한 단어가 더 많이 나타날수록 우선도를 높인다. (연관 키워드의 개수)
- 그러한 단어가 더 자주 나타날수록 우선도를 높인다. (연관 키워드의 빈도)
지금 생각해 보면 굉장히 단순한 명세였지만, 그 당시에는 너무 어렵게도 느껴졌다.
그때 구현한 내용을 여기에 쓰기엔 너무 길어지니 추후 작성하도록 하고 마저 이어가 보자.
저 당시 고민 했던 문제들이 있었다
- 게시물이 추가로 작성되는 것에 따른 연관도 변화는?
- 연관게시물도 저장을 해야 하나?
- 저장을 안 한다면 매번 계산하나?? 그럼 DB의 읽기 쓰기 부담은 어떡하지?
결국 위 문제들로 해결하지 못하고 명세조차 완벽히 만족시키지 못했다. 이후 자존감이 많이 떨어진 상태로 어떻게 공부를 해야 하나 방황하기 시작했다.
1.2. 아는 만큼 보인다.
정말 시대를 관통하는 명언이라고 생각한다.
1.1의 명세로 과제를 진행하고 많은 시간이 지나지 않았다 약 5개월의 시간이 지났지만 그동안 개발에 대해 꾸준히 공부해 온 결과 많은 것이 달라졌다.
다시 한번 말하지만 이번 프로젝트에서는 정말 많은 것이 달라졌다.
우선 키워드를 하나하나 분해해서 전체 게시물과 대조하던 나의 낡은 방법은 쓰레기통에 버려버렸다.
대신 TF-IDF라는 알고리즘으로 게시물을 벡터화하였다.
다음으론 코사인 유사도를 적용하여 연관도를 계산하려고 했으나,
표본이 너무 적은 관계로 차선책을 찾던 중 지난번 명세를 구현하지 못해 속이 쓰렸던 기억이 떠올랐다.
지금의 나는 구현할 수 있지 않을까? 하고 지난 명세를 뒤적이기 시작했다.
그랬더니 웬걸.. 지난번에 왜 못했을까 싶을 정도로 너무 쉽게 구현을 했다.
나아가 그 당시 해결 못했던 고민들에 대해서도 많은 인사이트들이 생겼다.
지금 당장 구현했던 방법도 있고, 앞으로 개선할 방법도 있다.
이것들에 대해 하나씩 소개하며 이제는 장황한 글쓰기가 아닌 정말 기술을 회고해 보도록 하자.
2. 구현
위에서 고민했던 내용들을 가져와보자 그리고 이것들을 어느 정도 해결한 나의 답안이 이번 기능의 완성된 모듈이다.
- 게시물이 추가로 작성되는 것에 따른 연관도 변화는?
- 연관게시물도 저장을 해야 하나?
- 저장을 안 한다면 매번 계산하나?? 그럼 DB의 읽기 쓰기 부담은 어떡하지?
꾸준히 공부하며 내가 스스로 세운 기준은
모든 것은 trade-off이며 합당한 근거를 가지고 사용자 혹은 개발 동료를 설득할 수 있어야 한 다이다.
따라서 나 스스로도 구현을 하며 끊임없이 방법에 대해 저울질하였고, 우선은 이 방식대로 구현하였으나 해내면서도 계속해서 더 좋은 방법이 없을까 하고 고민하게 되었다.
2.1. 게시물이 추가로 작성되는 것에 따른 연관도 변화는?
첫 번째 문제해결이다. 첫 구현 당시 어떻게 할지 몰라 전전긍긍하던 이것은
Spring Batch와 Scheduler로 해결했다.
이것을 구현하는 데 있어 나의 가장 큰 걸림돌은
모든 것은 완벽해야 한다였다.
무슨 말이냐면 즉, 새로운 게시물이 생기면 이전 게시물도 바로바로 연관게시물이 바뀌어야 한다는 것이다.
돌이켜보면 참 부족한 생각이었다. 글을 쓰는 사람이 한 둘도 아닌데 매번 새로운 글이 생길 때마다 연관도를 어떻게 계산하겠는가?
그 결과 이런 결론에 도달했다.
연관 게시물이란 엄청나게 정확하지 않아도 된다. 어느 정도의 텀을 두고 연관도를 계산하여 새로 매핑해 줘도 괜찮겠구나.
-> 그럼 그런 반복 작업을 어떻게 처리하지?
-> 아 Batch와 Scheduler에 대해 알게 됐는데 이걸로 해보자
라는 결론에 도달하여 Batch와 Scheduler로 이 문제를 해결하기로 결정했다.
2.2. 연관게시물도 저장을 해야 하나?
이 고민을 한 처음 배경은 이렇다.
매번 연관 게시물을 계산해서 출력하느니 연관게시물을 저장해 두면 빠르게 읽을 수 있지 않을까?
하지만 이 고민은 스스로에게 바로 깨지고 만다
그럼 연관도가 바뀌면 매번 또다시 저장을 해??
계속 고민하다, 결국 데이터가 많아지면 하나를 조회할 때마다 연관 게시물을 계산해서 가져오는 것은 무리라는 판단에
저장을 하기로 결정했다. 다만 2.3에서 말할 거지만 다른 방법으로 저장을 하기로 했다.
2.3. 저장을 한다면 매번 계산하나? 그럼 DB의 읽기 쓰기 부담은??
이 문제는 위의 2.1과 2.2의 복합물에 가깝다. 즉 2.1을 해결하면 명제의 전반부가 해결되고
2.2를 해결하면 명제의 후반부가 해결된다. 우리는 전반부는 해결하고 왔으니 이제 게시물을 저장하기로 한 이상 후반부를 해결해야 한다.
나는 이 문제를 캐싱을 통해 해결하기로 했다.
우선 Redis까지 뻗어나가진 못했기에 서버의 장애가 생기면 연관도는 다 날아간다.. 이 점은 인지하고 있고 개선사항에 넣어두었다.
당장은 캐싱에 대해 이야기하자.
DB에 계속적인 읽기 쓰기를 주기적으로 발생시키고, MSA로 전환한 우리의 서비스 특성상 동시성 이슈도 있을 수 있었다. 이를 해결하기에 락을 걸자니 연관 게시물 계산이 다 완료되기 전엔 그럼 뭘 못한다는 말이 된다.
그래서 나는 동시성 이슈가 상대적으로 적은 읽기는 하되, 쓰기는 캐시에 하여 속도를 높이고, 다음번 읽기는 캐시에서 가져오는 것으로 읽기 부담 또한 낮추기로 하였다.
2.4. 총합
위 2.1~3의 고민 사항들과 명세를 만족한 최종 완성본을 가지고 고민을 해결해 나간 과정을 좀 더 상세히, 그리고 이전의 코드를 가지고 얼마나 발전했는지 비교하는 것을 앞으로 작성할 것이다.
따라서 연관게시물의 구현법을 보고자 하거나 참고하고 싶은 독자는 이 글의 향후 방향성과 맞지 않을 수 있다.
이 시리즈는 문제를 해결해나가는 과정과 그리고 성장을 담은 회고가 될 예정이기 때문이다
🌟REFERENCE
연관게시물 구현에 있어 가장 큰 인사이트를 준 Naver 뉴스 AiRS이다.
https://media.naver.com/algorithm