프로젝트

기술 블로그 검색엔진 개발기 - 2

ri5 2023. 10. 12. 21:30

HTML 파싱

이전에 URL로 요청을 보내고 HTML까지 저장하는 데까지 마무리하고 다음은 저장한 HTML에서 특정 태그를 파싱하여 가져오는 것을 진행했다. 그과정에서 자바에서 제공해주는 joup라는 html parser를 활용하여 글자를 추출해오는데 여기서 가장 큰 걸림돌은 어떻게 해야 글내용만 추출해올 수 있을까? 였다. 모든 글이 각자 다른 플랫폼을 활용하고 있었고 각기 다른 컨텐츠 영역을 가지고 있었기 때문에 셀렉터나 아이디를 활용하여 글영역만 가져오기 쉽지 않았다.

전처리를 하기위한 선택지

  • 플랫폼마다 글영역의 아이디를 저장하고 등록된 플랫폼만 글을 파싱하여 전처리한다.
  • 데이터 품질을 신경쓰지 않고 모든 내용들을 파싱한다.
  • 게시글에 자주 사용되는 태그를을 중심으로 파싱하여 전처리 한다.
  • 머신러닝을 통해 본문만 인식할 수 있도록 학습시킨다.
  • 이전글 html과 현재글 html을 비교하여 중복된 태그는 전부 제거한다.

이와같이 수많은 방안들을 생각했지만 그중에서 가장 현실성 있는 선택지는 게시글에 자주 사용되는 태그의 내용을 중심으로 전처리하는 것이였다. 해당 선택지를 고르게 된 이유는 저 중에서 가장 개발 리소스를 아낄 수 있고 어느정도 데이터 품질을 보장할 수 있는 선택지였기 때문이였다. 일단 빨리 프로토타이핑을 진행하고 배포를 해야했기 때문에 머신러닝이나 고도화된 필터링을 하기에는 여건이 충분치 않았다.

 

끝없는 삽질

개발자들이 가장 복잡하면서 하기 귀찮은 것 중 하나는 바로 세팅일 것이다. 그중에서 가장 까다로웠던 것은 버전 호환 이슈였다. 현재 스프링 부트와 버전에 맞는 Spring Data Elasticsearch를 사용하니 컨테이너에 올려놓은 엘라스틱 서치와 호환하지 않았고 버전을 낮추니Document 모델이 비정상적으로 동작했다. 그래서 일단 공식문서를 따라서 이것저것 설정해보고 실행했봤는데도 제대로 동작하지 않아 어쩔 수 없이 ElasticRepository를 다중상속을 통해 처리하고 나중에 Spring Data Elastic구조를 뜯어보고 분석해보기로 정했다.

Tokenize의 장벽

일단 검색엔진을 개발하면서 첫번째로 문제였던 것은 제대로된 토큰화가 되지 않는다는 것이였다. 예시를 들어 "레디스는 훌륭한 캐시서버 중 하나이다." 라고 입력이 되면 "레디스는", "훌륭한", "캐시서버", "중", "하나이다" 식으로 토큰화가 되어 인덱싱 되었다. 이렇게 된다면 유저가 레디스라고 검색을 해도 레디스와 관련된 블로그들이 나오지 않게 되면서 검색기능이 제 역활을 하지 못하게 되는 것이다. 그래서 한글로된 불용어 데이터들을 찾아서 stop filter를 활용하여 개발을 진행하기로 했다.

 

여기저기 래퍼런스들을 찾아보다가 Nori라는 한글 토큰화에 특화된 분석기에서 불용어뿐만 아니라 여러 합성어나 복합어들을 제외해준다는 것을 알게되어서 바로 차용하게 되었다. 그 후 하나의 문제를 해결하니 바로 그다음 문제가 바로 튀어나왔다. 그것은 바로 영어로 된 단어는 따로 토큰화가 되지 않는 것이였다. 그래서 "Elasticsearch는 최고의 검색엔진이다" 라고 입력이 되면 "최고", "검색", "엔진"으로 토큰화가 된다는 것이다. 기술 블로그는 특성상 영어와 한글을 혼용하는 경우가 많은데 이렇게 되면 사용자가 "elasticsearch"라고 검색해도 해당 내용과 관련된 블로그 글이 노출되지 않기 때문에 검색기능이 제대로된 역활을 하지 못하는 것이다. 

 

생각해내거나 조언받은 혜안들

  • 본문을 모두 영타로 저장해서 영어 분석기를 활용한다.(카프카 -> "zkvmzk")
  • 영어 분석기와 한글 분석기를 동시에 활용하여 영어로 검색하면 영어 분석기로 조회하고 한글로 검색하면 한글 분석기로 검색한다.
  • 영어로 된 내용들을 한글로 번역하여 모두 저장한고 영어 키워드가 입력되면 한글로 번역된 키워드로 검색한다.
  • 멀티 필드를 통해 하나의 필드에서 한국어, 영어 필드를 분리하여 사용하여 검색한다.
  • 중간에 언어 인식 파이프 라인을 두고 언어별 인덱스를 만들고 분리한다. 

참고

 

Elastic Search (4) 인덱스 설정과 매핑 - Settings & Mappings

ref. https://esbook.kimjmin.net/ 위 링크의 내용을 요약, 정리합니다.

velog.io

문제를 해결하기 위해 여러가지 방안들을 찾아보면서 위와 같은 방법들을 생각해낼 수 있었고 이제는 트레이드 오프를 어떻게 해야될지 고려했어야 했다. 사실 정석으로 간다고 한다면 언어 인식 모델을 통해 학습시키는 것이 가장 이상적인 방향이지만 현재 나에게는 머신러닝에 대한 학습을 할 시간도 없었고 머신러닝 파이프라인까지 구축하기에는 비용이 너무 과다하게 들었다. 저 중에서 가장 리소스, 서버비용, 유지보수를 고려해봤을 때에는 멀티 필드를 구성하여 관리하는 것이 가장 이상적인 방향이였기에 선택하기로 했다. 

 

기타 트러블 슈팅

  • 스프링 부트와 Spring Data Elastic 버전 충돌
  • 네이버 클라우드에서 서버리스 환경은 자바 17버전 미지원
  • Spring Data Elastic에서 Match 쿼리 메서드 미지원

산넘어 산

이제 검색기능이 어느정도 완성이 되었기에 CSV파일을 받아서 데이터베이스에 저장하는 API를 비동기 처리하기 위해 서버리스 서비스를 알아보던 중 현재 개발하고 있는 자바버전을 호환하지 않는다는 것을 깨닫고 막혀버리고 말았다.. 배치 서버를 계속 띄우기에는 비용적으로 너무 낭비였고 서버간 필요한 건 디비에 CSV 저장할 때 순간뿐이였으니 계속 띄워두기에는 비용적으로 너무 낭비였다. 그래서 서버리스 서비스를 이용해 서버의 라이프 사이클만 관리하고 내부적으로 배치작업을 돌리도록 계획했다. 그러다 보니 API 서버와 배치서버가 분리가 되고 공통된 모듈형태로 도메인을 관리할 수 있도록 멀티모듈 형태의 프로젝트 구조를 가져감으로써 공통으로 사용되는 도메인은 분리해야했다. 이렇게 가다가는 제한된 기간에 배포할 수 없기 때문에 다시 해야할 작업리스트를 정리하고 배포를 어떻게 해야할지 고민해봐야할 것 같다.