1500ms에서 400ms로 RDBMS 환경에서 77만 자 대용량 텍스트 검색 최적화기
전문 검색 엔진(Elasticsearch)을 즉각 도입하기 어려운 현실적인 제약 속에서, MySQL의 FULLTEXT 인덱스와 Node.js 레벨의 쿼리 라우팅을 결합하여 검색 성능을 73% 향상시킨 하이브리드 검색 구현기.
대량의 데이터를 다루는 백엔드 시스템에서 '검색'은 피할 수 없는 난제입니다. 한마음과학원의 교재 데이터를 웹 서비스로 이관하는 프로젝트를 진행하며, 저는 최대 454페이지, 약 77만 자에 달하는 텍스트 데이터를 DB에 적재하고 이를 단락 단위로 검색해야 하는 미션을 부여받았습니다.
초기에는 가장 익숙한 방식인 LIKE 연산자를 활용하여 검색을 구현했지만, 서비스가 요구하는 성능에는 한참 미치지 못했습니다. 이 글에서는 1500ms에 달하던 검색 응답 지연 문제를 어떻게 MySQL FULLTEXT 인덱스와 하이브리드 라우팅 로직을 통해 400ms로 단축했는지, 그리고 이 과정에서 얻은 아키텍처적 고민을 공유합니다.
The Problem: Full Table Scan의 늪
$O(N)$의 시간 복잡도가 만드는 1.5초의 지연
파싱된 대용량 텍스트 단락들을 대상으로 LIKE '%검색어%' 쿼리를 실행했을 때, 평균 응답 시간은 약 1500ms를 기록했습니다. 데이터베이스가 77만 자에 달하는 텍스트 블록들을 처음부터 끝까지 모두 읽어내는 Full Table Scan을 수행했기 때문입니다. 데이터가 누적될수록 탐색 시간은 선형적으로 증가할 수밖에 없는 구조였고, 이는 사용자 경험(UX)을 심각하게 저해하는 크리티컬한 문제였습니다.
당장 빠르고 고도화된 검색을 위해 Elasticsearch 같은 전문 검색 엔진을 도입하는 방안도 고려했습니다. 하지만 한정된 프로젝트 일정과 인프라 예산이라는 현실적인 제약 앞에서, 외부 인프라 추가 없이 현재의 RDBMS(MySQL) 환경을 극한으로 활용하여 최적의 성능을 끌어내는 것이 백엔드 개발자로서 제가 내려야 할 합리적인 결정이었습니다.
The Solution: 역인덱스와 하이브리드 라우팅
1. 역인덱스(Inverted Index)의 도입
Full Table Scan의 병목을 해결하기 위해 MySQL의 FULLTEXT BOOLEAN MODE를 도입했습니다. 텍스트를 탐색할 때마다 문서를 뒤지는 것이 아니라, 데이터를 적재할 때 미리 단어 단위로 쪼개어 "어떤 단어가 어느 문서에 있는지" 색인(Index)을 만들어 두는 역인덱스 방식을 활용한 것입니다. 이를 통해 검색 복잡도를 획기적으로 낮출 수 있었습니다.
2. MySQL 설정의 한계와 Node.js 라우팅 설계
하지만 FULLTEXT 인덱스에는 한 가지 치명적인 제약이 있었습니다. 당시 MySQL 서버의 설정상 단어의 최소 길이(innodb_ft_min_token_size)가 3글자로 제한되어 있어, 2글자 이하의 단어는 인덱스를 탈 수 없었습니다.
이를 해결하기 위해 DB에만 의존하지 않고, **Node.js 애플리케이션 계층에서 검색 쿼리를 동적으로 분기(Routing)**하는 하이브리드 검색 아키텍처를 직접 설계했습니다.
// Express 컨트롤러 단의 하이브리드 라우팅 의사코드(Pseudo-code)
const searchKeyword = req.query.keyword;
const keywordLength = searchKeyword.length;
let searchResults;
if (keywordLength >= 3) {
// 3글자 이상: FULLTEXT BOOLEAN MODE 쿼리 실행 (초고속 역인덱스 검색)
searchResults = await BookRepository.searchByFullText(searchKeyword);
} else {
// 2글자 이하: 기존 LIKE 쿼리 실행 (Fallback)
searchResults = await BookRepository.searchByLike(searchKeyword);
}
이 로직을 통해 3글자 이상의 핵심 키워드들은 FULLTEXT 인덱스를 타게 하여 검색 속도를 극대화하고, 2글자 이하의 검색어는 기존 방식으로 처리되도록 방어 로직을 세웠습니다. 추가로, 정확히 일치하는 단락에 가중치를 부여하고 인접 단락을 비교하는 스코어링 로직을 결합해 검색의 정확도(Relevance)도 함께 끌어올렸습니다.
The Result & Retrospective
성능 73% 향상과 최적화의 증명
하이브리드 검색 라우팅을 적용한 결과, 기존 1500ms에 달하던 쿼리 응답 시간을 400ms 수준으로 단축시켰습니다. 제한된 환경과 리소스라는 제약 속에서도 애플리케이션 레이어와 DB 레이어의 특성을 결합하여 실질적인 비즈니스 임팩트를 만들어낸 의미 있는 최적화 경험이었습니다.
다음 단계(Next Step)를 위한 시야 확장
이 프로젝트는 검색과 대용량 데이터 처리에 대한 깊은 고민을 안겨주었습니다. 현재 관점에서 이 시스템을 한 단계 더 고도화한다면 다음과 같은 아키텍처 확장을 시도할 것입니다.
- 한국어 형태소 분석과 Elasticsearch: MySQL FULLTEXT는 기본적으로 띄어쓰기 기준이므로 한국어의 조사 처리 등에 취약합니다. 향후에는 Nori 형태소 분석기를 연동한 Elasticsearch를 도입하여, 완벽한 역인덱싱과 BM25 기반의 정교한 스코어링 시스템을 구축하고 싶습니다.
- Node.js 비동기 스트리밍 파싱: 당시 서버 부하를 방지하기 위해 로컬 스크립트로 77만 자의 데이터를 마이그레이션했습니다. 다시 설계한다면, Node.js의
fs.createReadStream을 활용한 청크(Chunk) 단위 스트리밍 처리와worker_threads를 이용한 CPU 집약적 정규식 파싱 분리를 통해, 메인 이벤트 루프 블로킹 없이 서버 내부에서 안전하게 대용량 데이터를 소화하도록 구현할 것입니다.