인턴으로 근무했던 회사에서, 챗봇 기능을 구현할 일이 있었다. 고객 반응 및 여론을 확인하기 위한 시장 조사를 자동화하는 툴을 개발하는 프로젝트였는데, 기획 과정에서 유저 인터뷰를 하던 중 시장 조사 리포트를 받아볼 때 리포트에 없는 더 풍부한 정보를 얻을 수 있도록 챗봇 기능이 있으면 좋을 것 같다는 의견이 있어 구현하게 되었다.
여기서 챗봇 기능은 시장 조사를 통해 수집된 DB의 게시글 본문, 댓글 반응 등의 데이터를 바탕으로 자연어 질문에 LLM이 답변해주는, 이른바 RAG 기능이었다. 보통 RAG는 임베딩 벡터를 활용해 벡터DB로 구현하는 경우가 많다. 하지만 우리는 벡터DB를 사용하기에는 아래와 같은 제약이 있었다.
- 인수인계 및 유지보수를 위해 사내 기술스택인 MySQL을 이미 사용 중이었다.
- 사내 전용 툴이기 때문에 활용 가능한 서버 인프라가 크지 않았다.
- 챗봇은 부가 기능이기 때문에 이 기능 하나 때문에 벡터DB라는 추가 인프라를 들이기엔 부담이 컸다.
그렇다면 벡터DB 없이, 이미 쓰고 있는 MySQL만으로 RAG를 구현할 수 있는 방법은 없을까? 그 답을 MySQL Full Text Search에서 찾아보기로 했다.
특정 키워드가 포함된 텍스트를 DB에서 찾을 때는 흔히 LIKE 연산자를 사용한다. 그래서 처음에는 LIKE 연산자 사용을 고려했었다.
- 하지만 초반 운영 시기에는 LIKE 연산자로도 충분히 가능하겠지만 DB에 저장된 데이터 양은 점점 방대해질텐데 그 때도 LIKE 연산자로 충분할까?
- 자연어 질문이 길고 복잡하다면?
- 검색 결과가 많으면 어떻게 관련도순으로 정렬하지?
이 모든 질문에 대한 답을 MySQL Full Text Search가 제공하고 있었다.
MATCH (col1, col2, ...) AGAINST (expr [search_modifier])
LIKE 연산자와 달리 Full Text Search는 인덱스(FULLTEXT INDEX)를 탈 수 있다. 검색 방식은 Natural Language Search, Boolean Search, Query Expansions Search 세 가지가 있었는데, 이 중 Natural Language Search가 기본값이고 나도 해당 모드를 사용했기 때문에 여기서는 이 부분만 간단하게 설명하도록 하겠다.
Natural Language Search는 FULLTEXT INDEX에 포함된 컬럼들을 대상으로 검색 문자열과 유사도를 계산해 연관성 점수를 반환하고, 기본적으로 이 점수가 높은 순으로 정렬해준다.
문제는 이 검색 문자열은 자연어이기 때문에 그대로 비교할 수는 없다는 점이다. 어떤 방식으로든 잘게 쪼개서(토큰화해서) 인덱스와 비교해야 하는데, MySQL Full Text Search에는 한국어, 중국어, 일본어를 지원하는 ngram parser가 내장되어 있다. ngram parser는 문자열을 연속된 n개의 글자(문자) 단위로 토큰화한다.
처음에는 MySQL Full Text Search에 이미 ngram parser가 내장되어 있다는걸 몰랐다. 그래서 LIKE 검색을 하듯 자연어 질문을 직접 단어 단위로 쪼개고, 불용어를 제거해서 검색 쿼리를 만들고, 이걸 MATCH AGAINST로 검색하는 방식을 구현했다.
문제는, 내가 이렇게 한번 쪼개놓은 단어들을 ngram parser가 다시 문자 단위로 토큰화한다는 데 있었다. 파싱이 이중으로 일어나면서 원래 문장이 가진 맥락과 순서 정보가 흐트러졌고, 그 결과 인덱스를 타고 있었는데도 속도는 LIKE 연산과 차이가 없고 검색 결과도 LIKE 연산 보다 빠뜨리는 결과물이 많았다. 검색 결과가 많을 때 관련도순으로 유의미하게 정렬되지도 않았다. MySQL Full Text Search의 장점이 모두 사라진 것이다.
그제야 뭔가 잘못된 방식으로 구현한 것 같다는 사실을 인지하고 MySQL Full Text Search를 제대로 찾아보았고, ngram parser의 존재를 알게되었다. 직접 파싱하던 로직을 걷어내고 ngram parser에게 위임하니, 검색 속도도 빨라지고 관련도 순 정렬도, 검색되는 결과물 품질도 좋아져서 상위 N개를 가져올 때 질문과 관련성이 높은 유의미한 내용이 포함될 확률이 높아졌다.

AGAINST('자연어 질문' IN NATURAL LANGUAGE MODE)의 파라미터로 전달한다. 한정된 인프라 안에서 있는 기능들을 최대한 활용해 RAG 챗봇을 완성한 건 뜻깊은 경험이었다. 제약이 있는 상황에서 방법을 찾아낸 과정 자체가 의미 있었다고 생각한다.
다만 사용하려는 기술을 먼저 명확히 파악하고, 성공 여부를 판단할 성능 지표를 미리 정한 뒤 개발을 시작했다면 구현 시간을 더 단축할 수 있었을 것 같다.