기획 측 요구사항
지난주 스프린트에 검색어 자동 완성 기능이 있었다. 리뷰를 작성하기 위해 장소를 검색하는 부분인데 사용자가 검색어를 입력하면 해당 검색어가 제목에 포함되어 있는 게시글을 제목, 썸네일 사진 등과 함께 모두 보여줘야 했다. 자세한 내용은 아래와 같다.
- 사용자가 검색 창에 타이핑을 하다가 0.6초 이상 액션이 없으면 지금까지 입력된 검색어를 제목에 포함한 게시물을 요청한다.
(이 부분은 프론트 분들이 하실 거라서 딱히 생각할 필요 없다. ) - 예를 들어 "태호 티스토리"를 입력하면 "태호 티스토리" / "태호" / "티스토리"를 제목에 포함한 모든 게시물을 사전 순으로 정렬
("태호 티스토리"에 대한 검색 결과 사전 순 + "태호"에 대한 검색 결과 사전 순 + "티스토리"에 대한 검색 결과 사전 순)
"박물관"을 검색하면 "가나 박물관", "다라 박물관", "박물관 최고" ,,,, 와 같은 게시물이 추천되어야 한다. (이 부분 중요) - 무한 스크롤로 사용자에게 정보 제공
고민됐던 부분
우선 듣자마자 떠오르는 방법은 단순하게 findAllByTitleContaing(String keyword) 같은 Jpa 메서드를 사용하거나 querydsl로 Like '%keyword%' 연산을 사용하는 것이다. 하지만 이 방법은 직접 DB로 쿼리를 날린다는 점이 매우 큰 문제점이다. 한명만 해도 0.6초마다 쿼리를 날릴 수 있는데 이것을 온전히 직접 데이터베이스를 조회하도록 하는 것은 너무 많은 부하를 줄 것 같았다.
회의가 끝나고 음... redis..? 라는 생각으로 집으로 돌아왔다.
다른 서비스 찾아보기
자동 완성 기능을 제공하는 서비스들을 일단 찾아보았다. 바로 생각난 것이 게임 롤 전적을 검색할 수 있는 op.gg와 인프런이 생각났다.
혹시나 얻어갈게 있을까 해서 인프런 자동완성의 response를 개발자 도구를 켜서 확인해보았다.
response를 보니 사용자의 입력을 띄어쓰기로 구분한 후 그 키워드를 각각 유의어, 한/영 잘못 누르고 쳤을 경우를 처리한 후 강의의 키워드를 갖고 있는 slug와 비교해서 값을 보내주는 것 같았다.. 사용자의 입력에 대한 여러 상황을 대비하는 것은 사용하기 위해 메모...
(한/영 잘못 누르고 입력한 경우를 미리 입력해 놓는 것과 강의에 키워드를 저장하는 것 같은 slug는 사람이 직접 작성해 놓는 건가..? 어쨌든 정말 친절한 검색 엔진이다... 실무는 정말 거대하고 크구나)
인프런에서 사용자가 입력하는 동안, 검색 결과가 도착하는 동안 나오는 로딩 페이지를 보고 일단 기획자분들한테 우리의 데이터가 많지는 않아 로딩 상황이 자주 있을 것 같지는 않지만 있으면 좋을 것 같아 요청드렸다.
구현 과정
한 이틀 동안은 자동 완성은 구현 시작도 못하고 방법을 찾아본 것 같다. 시간의 흐름대로 나열해 보면
1. Trie 자료 구조
자동 완성 방법을 찾다 보면 제일 많이 나오는 것이 Trie 자료 구조이다. 트리 형태의 자료 구조이다. 생각보다 쉽게 해결하겠는데?(라고 생각하면 항상 난관이 있다.)하고 몇 년 전에 학교에서 배웠기 때문에 trie 구조에 대한 공부를 간단하게 하고 구현을 하려 했는데,,,,
Trie는 prefix에 대한 자동 완성만 가능하다.... "가"라고 검색하면 가로 시작하는 제목을 검색해 주기 때문에 나에게 주어진 요구사항과 맞지 않아서 폐기.
2. Redis Hash (당첨)
처음 생각한 방법은 유저가 검색어를 입력했을 때 그 검색어가 redis에 있으면 redis에서 값을 가져오고 아니면 DB에서 조회 후 redis에 저장 즉 Cache hit/miss를 활용하려 했다. 하지만 문제 점이 몇 개 떠올랐다.
- 사용자가 많아야 hit의 비율이 높을 것이고 게다가 우리 서비스는 4개 국어를 지원한다. 박물관이라는 키워드 하나를 한국어, 영어, 베트남어, 중국어로 나눠서 저장하기 때문에 cache hit이 발생할 경우가 너무 적어 보인다.
- 자동 완성 시 사진, 주소, 제목 등 여러 요소를 보여줘야 하기 때문에 객체를 저장해야 한다. (String 사용 불가)
언어 별로 공간을 만들고, title(key) - 자동 완성 추천 객체(value) 형태로 저장을 해서 검색어와 key를 비교해서 포함하면 value를 보여주면 될 것 같은데......! 이렇게 Hash가 적합도 100으로 선택되었다.
우선 @PostConstruct를 사용해 서버가 켜질 때 자동 완성을 해줘야 하는 모든 게시물들을 각자의 언어에 맞는 Hash에 저장한다.
위의 코드가 실행되면 그림과 같이 게시물이 redis에 저장된다. 자동 완성을 위한 준비는 끝났고 자동 완성의 동작 방식은
- 사용자가 입력한 검색어를 원본, 공백 제거, 공백을 기준으로 split한 문자들을 키워드로 만든다.
- 사용자의 언어를 바탕으로 hash를 선택한다, 해당 hash의 모든 key들을 가져와서 1에서 만든 키워드를 포함한 key가 있는지 찾아서 포함하는 경우 response에 담는다.
- 2에서 중복 결과 값이 발생할 수 있으니 중복 제거.
구현을 완료 했으나 찝찝함이 있었다. key를 모두 가져와서 직접 비교하다보니 효율적이지 못하다고 생각했다. 데이터가 (언어 당) 600~700개 정도이고 로딩 화면도 요청했기 때문에 웬만해서는 성능에 문제가 생길 것 같지는 않았지만 더 나은 방법이 있나 실험해보고 싶어서 일단 git에 push 해놓고 다른 방안을 찾아보았다.. (결국 Redis hash 이 방법으로 서비스 진행 중!)
3. Elasticsearch
검색 엔진으로는 워낙 유명해서 이름은 들어보았지만 공부해본적은 없어서 살짝 맛보았다. 이 과정을 간단히만 기록해보겠다.
elasticsearch는 역색인 구조로 이루어지는데 이것이 자동 완성에 너무 적절해보였다!
위와 같이 텍스트를 분석기를 사용해 나누어서 단어별로 색인 페이지를 만들어 준다. (책 맨 뒤에 단어 적혀있고 그 단어가 나온 페이지 나와있는 것과 같다.) 열심히 구글링하며 코드까지 다 작성했지만 결국 redis로 돌아가게 됐다..스프린트 기간에 맞춰 완벽하게 완성할 자신이 없었다. 기간을 늘려준다고 해도 아래와 같은 걸림돌 때문에 많은 공부와 시도가 필요할 것 같았다.
- 버전이 예민하다(?) - 너무 급하게 해서 그런 것 같기는 하지만 여러 글을 보고 설치를 해보면서 느낀 것이 springboot와 elasticserach 설치부터 쉽지가 않았다. 기껏 작성해 놓은 코드가 제대로 돌아간 적이 한번도 없었다.
- EC2에 elasticsearch 설치 - 여차저차 로컬에서 성공했다고 하더라도 이걸 ec2에 적용하려면 최소 2주는 붙잡고 있어야할 것 같다는 느낌이 들었다. 열심히 스프린트 달리는 중이기 때문에 불가능.. (2주를 줬어도 성공했을까..?)
- 우리 서비스는 다국어를 지원한다 - 위의 역색인 구조를 사용하기 위해 형태소 분석이 필요하다. 한국어 형태소 분석에는 nori가 대표적으로 사용되는 것 같았지만 중국어, 말레이시아어까지 해결하려면 많은 시간이 필요해보였다.
이런 이유로 적용은 못했지만 그래도 elasticsearch 관련된 글을 처음 읽어봤고 맛만 봤지만 흥미로운 주제였다. (언젠가는 로컬에서 혼자라도 꼭 써본다.)
마무리
처음에 막막했지만 나름 동작도 잘하고 있다! elasticsearch에 대한 호기심이 증폭되어서 시간나면 로컬에서 한번 한국어만이라도 적용해 보고 싶다.
끝~
'My project > Nanaland in Jeju' 카테고리의 다른 글
프로젝트 진행 시 엑셀에 작성한 데이터, DB에 저장하는 방법 (0) | 2024.10.17 |
---|---|
스프링 검색어 자동완성 비동기 처리 (Feat. 1만번의 부하테스트 결론은 Over Engineering 이었다고 한다..) (5) | 2024.08.29 |
Stream API 사용 중 .toList()에서 UnsupportedOperationException 발생한 썰 (0) | 2024.07.26 |
QueryDsl 부모에 자식 리스트 넣기 -> Transform (부제 : 쿼리의 수와 속도는 무조건 정비례가 아니다..) (0) | 2024.06.27 |
맥에서 MySql, MaraiDB 두개 다 사용하기 (Feat. Docker 이래서 쓰는구나, 맥 mariaDB 설치하기) (0) | 2024.03.27 |