FTS5 검색, 자동 완성(Auto-Suggest) 및 일일 스캔을 활용한 도서 키워드 인덱스 구축
요약
SQLite의 FTS5를 활용하여 외부 검색 엔진 없이 효율적인 도서 키워드 인덱스 및 자동 완성 기능을 구축하는 방법을 소개합니다. 접두사 쿼리와 델타 동기화 방식을 통해 성능과 데이터 최신성을 동시에 확보하는 기술적 접근법을 다룹니다.
핵심 포인트
- SQLite FTS5를 이용한 전문 검색 및 자동 완성 구현
- content= 옵션을 활용한 정규화된 데이터와 역색인 간의 매핑
- 접두사 연산자(*)를 통한 빠른 자동 완성(Auto-suggest) 처리
- 전체 재구축 대신 델타 동기화를 통한 효율적인 일일 인덱스 업데이트
지식 관리 시스템(Knowledge management systems)에서 텍스트 검색만으로는 충분한 경우가 드뭅니다. 사용자들은 즉각적인 제안(Suggestions)과 최신 상태의 인덱스를 기대합니다. 우리의 도서 키워드 인덱스 모듈은 SQLite의 FTS5를 활용하여 전문 검색(Full-text search)을 수행하고, 접두사 쿼리(Prefix queries)를 통해 자동 완성(Auto-suggest)을 구현하며, 인덱스를 최신 상태로 유지하기 위해 매일 스캔을 실행합니다. 추가적인 의존성 없이 이를 어떻게 구축했는지 소개합니다.
핵심 요구사항은 간단했습니다. 관계형 스키마(Relational schema)에서 도서 키워드를 인덱싱하고, 빠른 조회(Lookups)를 지원하며, 사용자가 타이핑할 때 제안을 반환하는 것이었습니다. SQLite의 가상 테이블 모듈인 FTS5가 이 무거운 작업을 처리합니다. 우리는 content=를 사용하여 외부 테이블과 연결함으로써 메인 키워드 데이터에 매핑되는 FTS5 테이블을 생성합니다. 이를 통해 소스 데이터는 정규화(Normalized)된 상태를 유지하면서, FTS5가 역색인(Inverted index)을 관리하도록 합니다.
CREATE VIRTUAL TABLE keywords_fts USING fts5(
keyword,
book_id UNINDEXED,
...
tokenize 파라미터는 악센트를 제거하고 유니코드(Unicode)를 정규화하는데, 이는 다국어 도서 메타데이터(Metadata)에 있어 매우 중요합니다. 자동 완성(Auto-suggest)을 위해 우리는 접두사 연산자(Prefix operator)로 FTS5를 쿼리합니다. 사용자가 "neur"라고 입력하면, 검색어 뒤에 *를 붙입니다. FTS5의 BM25 랭킹(Ranking)은 부분 쿼리(Partial queries)에 대해서도 상위 일치 항목을 빠르게 반환합니다.
SELECT keyword, rank
FROM keywords_fts
WHERE keywords_fts MATCH 'neur*'
...
FTS5 접두사 쿼리는 동일한 역색인을 활용하기 때문에 효율적입니다. 오래된 데이터가 표시되는 것을 방지하기 위해, 소스 테이블의 별도 타임스탬프(Timestamp) 컬럼을 사용하여 최근 30일 이내에 업데이트된 키워드로 제안을 제한합니다. 쿼리는 FTS 테이블을 소스 테이블과 조인(Join)하여 updated_at 기준으로 필터링합니다.
세 번째 요소는 일일 스캔(Daily scanning)입니다. 예약된 백그라운드 작업(단순한 cron 항목 또는 systemd timers와 같은 스케줄러를 통해 구현됨)이 rebuild_fts_index()를 트리거합니다. 이 함수는 마지막 스캔 이후 수정된 모든 키워드를 읽고, content= 동기화(sync)를 사용하여 FTS 테이블에서 오래된 행을 삭제한 뒤 새로운 행을 삽입합니다. 핵심적인 통찰은 전체 재구축(full rebuild)을 피하는 것입니다. 즉, content=가 설정되었을 때 FTS5가 제공하는 UPDATE 및 DELETE 핸들러를 통해 델타 동기화(delta syncs)만 수행합니다. 일일 스캔은 소스 테이블에 INSERT OR REPLACE를 호출하여 이러한 핸들러를 자동으로 트리거합니다.
-- 일일 cron에 의해 호출되는 함수 예시
INSERT OR REPLACE INTO keywords (book_id, keyword, updated_at)
VALUES (?, ?, datetime('now'));
...
우리는 스택을 단순하게 유지하고 의존성(dependency) 개수를 낮추기 위해 외부 검색 엔진(Elasticsearch 등)을 사용하지 않았습니다. 수백만 행 미만의 대부분의 도서 데이터베이스에서 FTS5는 훌륭한 성능을 보여줍니다. 자동 완성(auto-suggest) 지연 시간은 10ms 미만으로 유지되며, 일일 스캔은 몇 초 내에 완료됩니다.
한 가지 주의할 점은, FTS5가 가상 테이블(virtual table)을 통해 직접적인 증분 업데이트(incremental updates)를 지원하지 않는다는 것입니다. 대신 트리거(trigger)나 content= 동기화에 의존합니다. 우리의 일일 스캔은 updated_at 필드가 설정되도록 보장하며, 소스 테이블의 트리거가 삽입(insertion) 및 삭제(deletion) 시 FTS5가 동기화 상태를 유지하도록 합니다. 스캔 중에 데이터 손상이 감지되면 전체 테이블에 대해 FTS 인덱스를 재구축하기도 하지만, 이는 드문 일입니다.
이 모듈은 간단한 API를 통해 더 넓은 지식 관리 시스템(knowledge management system)과 통합됩니다. 검색 엔드포인트(search endpoint)는 q 파라미터를 수용하고, 제안 엔드포인트(suggest endpoint)는 완성된 검색어의 JSON 배열을 반환하며, 스캔은 내부 서비스에 의해 호출됩니다. 이러한 설계는 코드베이스를 모듈화하고 테스트 가능하게 유지합니다.
숙련된 개발자들에게 트레이드오프 (trade-offs)는 명확합니다. FTS5는 분산 기능 (distributed capabilities)과 고급 스코어링 (advanced scoring) 기능이 부족하지만, 적당한 데이터 규모를 가진 단일 머신 설정에서는 실용적인 선택입니다. 자동 완성 (auto-suggest) 접두사 쿼리 (prefix query)는 끝에 와일드카드 (wildcard)가 필요하며, 이는 빈 결과가 나오는 것을 방지하기 위해 최소 토큰 길이 필터 (minimum token length filters)로 커스텀할 수 있습니다. 일일 스캔 (Daily scanning)은 파일 기반 뮤텍스 (file-based mutex)를 통해 처리되는 단순한 잠금 파일 (lock file)을 사용하여 동시 실행을 방지합니다.
운영 환경에서 이 시스템은 하루에 수천 개의 쿼리를 문제없이 처리합니다. 진짜 교훈은 항상 무거운 검색 인프라 (search infrastructure)가 필요한 것은 아니라는 점입니다. 때로는 잘 조정된 FTS5 테이블과 일일 크론 잡 (cron job)만으로도 충분합니다. 적절한 필드를 인덱싱 (indexing)하고, 데이터셋에 맞춰 토크나이저 (tokenizer)를 튜닝하며, 동기화 (sync)를 정밀하게 유지하는 데 집중하십시오.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기