본문 바로가기
WIKI 기술 지식 베이스

전문 검색

원문 보기 위키 갱신

Turso는 Tantivy 엔진으로 구동되는 전문 검색(FTS)을 제공해요. 커스텀 FTS 인덱스와 세 가지 SQL 함수 — 필터링용 fts_match, 관련도 순위 매기기용 fts_score, 검색어 하이라이트용 fts_highlight — 로 검색을 구현할 수 있어요.

출처: 문서

본문

**Turso 확장 기능**: Turso의 전문 검색(FTS)은 SQLite의 FTS3/FTS4/FTS5 모듈이 아니라 [Tantivy](https://github.com/quickwit-oss/tantivy)로 구동돼요. 문법과 함수도 SQLite FTS와 달라요.

Turso는 커스텀 FTS 인덱스와 세 가지 SQL 함수로 전문 검색을 제공해요: 필터링용 fts_match, 관련도 순위용 fts_score, 일치 용어를 강조해서 보여주는 fts_highlight.


FTS 인덱스 만들기

FTS 인덱스는 CREATE INDEX에 USING fts 절을 붙여서 만들어요. 인덱스에 넣은 각 컬럼이 전문 검색에 참여해요.

CREATE INDEX index_name ON table_name USING fts (column1, column2, ...);

토크나이저 설정

WITH 절로 컬럼마다 다른 토크나이저를 쓸 수 있어요.

-- All columns use the default tokenizer
CREATE INDEX idx_articles ON articles USING fts (title, body);

-- Per-column tokenizer configuration
CREATE INDEX idx_articles ON articles USING fts (
    title WITH tokenizer=simple,
    body WITH tokenizer=ngram
);

-- Global tokenizer for all columns
CREATE INDEX idx_tags ON tags USING fts (tag) WITH (tokenizer = 'raw');

사용 가능한 토크나이저

토크나이저 설명 용도
default 소문자 변환과 문장부호 분리를 하는 유니코드 인식 토크나이저 (40자 제한) 범용 텍스트 검색
raw 토큰화하지 않음 -- 필드 값 전체를 하나의 토큰으로 취급 ID, UUID, 태그, 정확 일치 필드
simple 소문자 변환 없이 공백과 문장부호로 분리 대소문자 무시가 필요 없는 단순 텍스트
whitespace 공백으로만 분리 공백으로 구분된 토큰
ngram 텍스트에서 2-3자 n-그램을 생성 자동완성, 부분 문자열 매칭

토크나이저 예제:

입력 default raw ngram
Hello World hello, world Hello World He, Hel, el, ell, ll, llo, ...
user-123 user, 123 user-123 us, use, se, ser, ...

필드 가중치

인덱스 컬럼에 상대 가중치를 부여해서 BM25 관련도 점수에 영향을 줄 수 있어요.

CREATE INDEX idx_articles ON articles USING fts (title, body)
    WITH (weights = 'title=2.0,body=1.0');
매개변수 기본값 설명
weights 모든 필드 1.0 쉼표로 구분된 column=weight 쌍. 가중치가 높을수록 점수 기여도가 커져요.

함수들

fts_match

행이 전문 검색 쿼리와 일치하면 1, 아니면 0을 반환해요. WHERE 절에서 행을 걸러낼 때 써요.

fts_match(column1, column2, ..., query)
매개변수 타입 설명
column1, column2, ... TEXT FTS 인덱스가 뒤받쳐주는 컬럼 하나 이상
query TEXT 검색 쿼리 문자열 (아래 쿼리 문법 참고)

반환: INTEGER -- 일치하면 1, 아니면 0.

fts_match에 넘기는 컬럼은 반드시 기존 FTS 인덱스의 컬럼과 대응돼야 해요. Turso의 쿼리 플래너가 WHERE 절에서 fts_match를 감지하면, 효율적인 조회를 위해 쿼리를 FTS 인덱스 쪽으로 보내요.

SELECT id, title FROM articles
WHERE fts_match(title, body, 'database');

-- Single column
SELECT id, title FROM articles
WHERE fts_match(body, 'machine learning');

fts_score

일치하는 각 행에 대해 BM25 관련도 점수를 계산해요. 점수가 낮을수록 관련도가 높아요.

fts_score(column1, column2, ..., query)
매개변수 타입 설명
column1, column2, ... TEXT FTS 인덱스가 뒤받쳐주는 컬럼 하나 이상
query TEXT 검색 쿼리 문자열

반환: REAL -- BM25 관련도 점수. 값이 낮을수록 관련도가 높아요.

결과 순서 취향에 따라 ORDER BY score ASC 또는 ORDER BY score DESC를 쓰면 돼요. SELECT에서 사용하면 FTS 인덱스가 자동으로 점수 매긴 결과를 제공해요.

SELECT
    id,
    title,
    fts_score(title, body, 'database') AS score
FROM articles
ORDER BY score DESC
LIMIT 10;

fts_highlight

일치하는 쿼리 용어를 지정한 태그로 감싼 텍스트를 반환해요. 검색 결과에서 일치 용어를 시각적으로 강조해 보여줄 때 유용해요.

fts_highlight(column1, column2, ..., open_tag, close_tag, query)
매개변수 타입 설명
column1, column2, ... TEXT 하이라이트할 텍스트 컬럼 하나 이상
open_tag TEXT 각 일치 용어 앞에 넣을 태그 (예: '<b>')
close_tag TEXT 각 일치 용어 뒤에 넣을 태그 (예: '</b>')
query TEXT 검색 쿼리 문자열

반환: TEXT -- 일치 용어가 지정한 태그로 감싸진 입력 텍스트. 일치가 없으면 원본 텍스트를 반환해요. query, open_tag, close_tag 중 하나라도 NULL이면 NULL을 반환해요.

여러 컬럼을 넘기면 그 텍스트들을 공백으로 이어 붙여요.

SELECT fts_highlight(title, '<b>', '</b>', 'database') AS highlighted
FROM articles
WHERE fts_match(title, body, 'database');
-- <b>Database</b> Design Patterns

-- Multiple columns are concatenated
SELECT fts_highlight(title, body, '<mark>', '</mark>', 'database') AS highlighted
FROM articles
WHERE fts_match(title, body, 'database');

-- Standalone usage (without an FTS index)
SELECT fts_highlight('The quick brown fox', '<em>', '</em>', 'quick fox');
-- The <em>quick</em> brown <em>fox</em>

쿼리 문법

fts_match와 fts_score에 넘기는 쿼리 문자열은 Tantivy의 쿼리 파서 문법을 지원해요.

기본 쿼리

문법 예제 설명
단일 용어 'database' "database"가 들어간 행 매칭
여러 용어 (OR) 'database search' "database" 또는 "search"가 들어간 행 매칭
불리언 AND 'database AND search' 두 용어가 모두 들어간 행 매칭
불리언 NOT 'database NOT nosql' "database"는 매칭하되 "nosql"은 제외

고급 쿼리

문법 예제 설명
구(phrase) 검색 '"exact phrase"' 정확한 구를 매칭
접두사 검색 'data*' "data"로 시작하는 용어 매칭
컬럼 지정 'title:database' title 필드에서만 "database" 매칭
부스팅 'title:database^2' title에서의 매칭에 2배 가중치 부여

예제

-- Simple term search
SELECT * FROM articles WHERE fts_match(title, body, 'database');

-- Phrase search: match "full text search" as an exact phrase
SELECT * FROM articles WHERE fts_match(title, body, '"full text search"');

-- Boolean AND: both terms must be present
SELECT * FROM articles WHERE fts_match(title, body, 'database AND performance');

-- Prefix search: match "optim", "optimize", "optimization", etc.
SELECT * FROM articles WHERE fts_match(title, body, 'optim*');

-- Column-specific: only match "rust" in the title field
SELECT * FROM articles WHERE fts_match(title, body, 'title:rust');

-- Boosted search: title matches count 2x more toward the score
SELECT id, title, fts_score(title, body, 'title:database^2 body:database') AS score
FROM articles
ORDER BY score DESC;

-- Exclusion: match "database" but not "nosql"
SELECT * FROM articles WHERE fts_match(title, body, 'database NOT nosql');

전체 예제

이 예제는 테이블 만들기, FTS 인덱스 추가, 데이터 삽입, 점수 매기기와 하이라이트를 곁들인 쿼리 실행까지 차례로 살펴봐요.

-- Create a table
CREATE TABLE articles (
    id INTEGER PRIMARY KEY,
    title TEXT NOT NULL,
    body TEXT,
    author TEXT
);

-- Create an FTS index on title and body with field weights
CREATE INDEX idx_articles_fts ON articles USING fts (title, body)
    WITH (weights = 'title=2.0,body=1.0');

-- Insert sample data
INSERT INTO articles VALUES (1, 'Introduction to Databases', 'Databases store and organize data for efficient retrieval.', 'Alice');
INSERT INTO articles VALUES (2, 'Full-Text Search in Practice', 'Full-text search allows finding documents by content.', 'Bob');
INSERT INTO articles VALUES (3, 'Database Performance Tuning', 'Optimizing database queries requires understanding indexes.', 'Alice');
INSERT INTO articles VALUES (4, 'Getting Started with Rust', 'Rust is a systems programming language focused on safety.', 'Carol');

-- Simple search: find articles mentioning "database"
SELECT id, title FROM articles
WHERE fts_match(title, body, 'database');
-- 1 | Introduction to Databases
-- 3 | Database Performance Tuning

-- Ranked search: order by relevance
SELECT
    id,
    title,
    fts_score(title, body, 'database') AS score
FROM articles
WHERE fts_match(title, body, 'database')
ORDER BY score DESC
LIMIT 10;

-- Highlighted results
SELECT
    id,
    fts_highlight(title, '<b>', '</b>', 'database') AS title,
    fts_highlight(body, '<b>', '</b>', 'database') AS body
FROM articles
WHERE fts_match(title, body, 'database');

-- Combine FTS with regular SQL filters
SELECT id, title, author,
    fts_score(title, body, 'database') AS score
FROM articles
WHERE fts_match(title, body, 'database')
  AND author = 'Alice'
ORDER BY score DESC;

인덱스 유지 관리

OPTIMIZE INDEX

모든 Tantivy 세그먼트를 하나의 최적화된 세그먼트로 병합해요. 쿼리 성능을 개선하고 저장 오버헤드를 줄여줘요. 특히 대량 삽입 이후에 효과가 커요.

-- Optimize a specific FTS index
OPTIMIZE INDEX idx_articles_fts;

-- Optimize all FTS indexes in the database
OPTIMIZE INDEX;

언제 쓰면 좋아요:

  • INSERT 문을 많이 쓰는 대량 데이터 가져오기 이후
  • 시간이 지나면서 쿼리 성능이 떨어졌을 때
  • 예정된 유지 보수 창구 작업 중에

하는 일:

  • 대기 중인 문서를 디스크로 플러시
  • 모든 세그먼트를 하나의 세그먼트로 병합
  • 삭제된 문서 톰스톤 제거
  • 새로 읽을 수 있도록 내부 캐시 무효화

DML과 인덱스 갱신

기반 테이블을 수정하면 FTS 인덱스가 자동으로 갱신돼요.

연산 FTS 동작
INSERT 새 행은 즉시 인덱싱돼요 (1000개 문서마다 배치 커밋)
UPDATE 내부적으로 DELETE + INSERT로 구현돼요
DELETE 톰스톤으로 문서를 삭제 표시하고, OPTIMIZE 때 정리돼요
-- All of these automatically update the FTS index
INSERT INTO articles VALUES (5, 'New Article', 'Content here.', 'Dave');
UPDATE articles SET body = 'Updated content.' WHERE id = 5;
DELETE FROM articles WHERE id = 5;

SQLite FTS5와 비교

기능 SQLite FTS5 Turso FTS
Filtering WHERE t MATCH 'query' WHERE fts_match(cols, 'query')
Ranking bm25(t), rank column fts_score(cols, 'query')
Highlighting highlight(t, col_idx, open, close) fts_highlight(cols, open, close, query)
Snippets snippet(t, ...) Not supported
Boolean operators AND, OR, NOT AND, OR, NOT
Phrase search "exact phrase" "exact phrase"
Prefix search word* word*
Column filter col:term col:term
Tokenizers unicode61, ascii, porter default, raw, simple, whitespace, ngram
Segment management Automatic Manual via OPTIMIZE INDEX
Transaction visibility Immediate After COMMIT

현재 제한 사항

  • snippet 함수 없음: 용어 강조는 fts_highlight를 쓰세요. 컨텍스트 스니펫은 아직 제공되지 않아요.
  • 자동 세그먼트 병합 없음: 대량 쓰기 후에는 주기적으로 OPTIMIZE INDEX를 실행하세요.
  • 트랜잭션 안 read-your-writes 없음: 트랜잭션 안에서의 FTS 변경은 트랜잭션이 커밋되기 전까지는 쿼리에 보이지 않아요. ROLLBACK은 테이블 변경과 FTS 변경을 모두 올바르게 버려요.
  • MATCH 연산자 문법 없음: WHERE table MATCH 'query' 대신 fts_match() 함수 호출을 쓰세요.

더 알아보기 (Learn more)