전문 검색#
개요#
AgensGraph는 PostgreSQL 위에 만들어졌고 그 전문 검색 엔진을 그대로 물려받으므로, 그래프 속성에 저장한 텍스트를 상대로 언어적 키워드 검색을 곧바로 할 수 있습니다. 전문 검색은 문서와 쿼리를 어휘소로 정규화합니다(어간 추출·불용어 제거·대소문자 접기). 그래서 사용자가 입력한 단어를 대조하는 어휘 검색에 잘 맞습니다.
의미로 대조하는 벡터 검색과 자연스럽게 짝을 이룹니다. 둘을 결합하는 것은 하이브리드 검색이 다룹니다. 세 페이지가 같은 영화를 쓰므로 결과를 직접 견주어 볼 수 있습니다.
그래프 속성은 jsonb로 저장되므로, 텍스트 속성은 tsvector로 바꾸기 전에 (::text로)
text로 형 변환합니다.
예제 데이터#
예제는 예제 데이터셋을 씁니다. 영화 18편이 각각 plot과
그 줄거리의 4차원 embedding을 가지고 있습니다. 더 가기 전에 그것을 적재하십시오.
실제 임베딩은 text-embedding-3-small이나 로컬 sentence-transformers 모델 같은 모델에서 나오며
차원이 수백 개입니다. 여기서는 지면에서 기하 구조가 읽히도록 넷으로 줄였습니다.
텍스트 속성 검색하기#
to_tsvector로 속성을 tsvector로 바꾸고, *_to_tsquery 함수 중 하나로 사용자의 입력을 파싱한
뒤, @@ 연산자로 대조하십시오. websearch_to_tsquery는 너그러운 웹 스타일 구문을 받습니다.
따옴표로 감싼 구절, OR, 그리고 단어를 빼는 -입니다. 그래서 사용자 입력에는 이것이 좋은
기본값입니다.
MATCH (m:movie)
WHERE to_tsvector('english', m.plot::text) @@ websearch_to_tsquery('english', 'machines')
RETURN m.title AS title;
title
----------------------------
"The Matrix"
"The Matrix Reloaded"
"The Matrix Revolutions"
"The Matrix Resurrections"
(4 rows)
쿼리는 원문 텍스트가 아니라 어휘소와 대조됩니다. machines는 어간 machin으로 줄어들므로 단수형
machine도 정확히 같은 행과 일치합니다.
반환되지 않은 것도 보십시오. Total Recall과 The Terminator는 주제로 보면 기계가 지배하는 미래를 다루지만 줄거리에 그 단어를 쓰지 않았으므로 키워드 검색이 찾지 못합니다. 그 재현율의 빈틈이 바로 벡터 검색이 돕는 지점입니다.
기대하는 입력에 따라 다른 쿼리 파서도 쓸 수 있습니다:
함수 |
입력 형태 |
|---|---|
|
웹 스타일: |
|
평문이며 모든 단어를 |
|
연산자를 직접 씁니다: |
결과 순위 매기기#
ts_rank는 각 문서가 쿼리에 얼마나 잘 맞는지 점수를 매기므로 결과를 관련도로 정렬할 수 있습니다.
쿼리 단어를 더 많이 맞힌 줄거리가 더 높은 순위를 받습니다:
MATCH (m:movie)
WHERE to_tsvector('english', m.plot::text) @@ websearch_to_tsquery('english', 'machines OR reality')
RETURN m.title AS title,
ts_rank(to_tsvector('english', m.plot::text),
websearch_to_tsquery('english', 'machines OR reality')) AS rank
ORDER BY rank DESC;
title | rank
----------------------------+-------------
"The Matrix" | 0.06079271
"The Matrix Reloaded" | 0.030396355
"The Matrix Revolutions" | 0.030396355
"The Matrix Resurrections" | 0.030396355
(4 rows)
The Matrix가 속편들보다 위에 오는 것은 그 줄거리가 machines와 reality를 둘 다 담고 있는
반면 속편들은 machines만 맞히기 때문입니다.
정확한 단어 대조#
쿼리가 정확히 맞아야 하는 특정 단어 — 이름, 코드, 드문 키워드 — 일 때는 전문 검색이 알맞은 도구입니다. dinosaurs에 관한 영화를 물으면 정확히 하나를 돌려줍니다:
MATCH (m:movie)
WHERE to_tsvector('english', m.plot::text) @@ websearch_to_tsquery('english', 'dinosaurs')
RETURN m.title AS title;
title
-----------------
"Jurassic Park"
(1 row)
벡터 검색에는 dinosaurs라는 글자 그대로의 단어라는 개념이 없습니다.
Jurassic Park과 가장 비슷한 영화를 물으면 가장 가까운 이웃으로 The Avengers를 돌려줍니다. 둘 다
대규모 볼거리 블록버스터이니 의미로는 그럴듯한 짐작이지만, “dinosaurs”를 입력한 사람이 원하는
것은 아닙니다:
MATCH (m:movie), (q:movie {title: 'Jurassic Park'})
RETURN m.title AS title
ORDER BY m.embedding::vector(4) <=> q.embedding::vector(4)
LIMIT 3;
title
-----------------
"Jurassic Park"
"The Avengers"
"Interstellar"
(3 rows)
그러므로 두 방법은 서로 반대쪽에 사각지대를 가집니다. 전문 검색은 정확하지만 동의어와 의도를 놓치고, 벡터 검색은 의미를 잡아내지만 정확한 단어에서 흔들립니다. 하이브리드 검색은 둘 다 실행하고 결과를 합쳐 각각의 강점을 취합니다.
GIN으로 인덱싱하기#
검색할 때마다 모든 행에 대해 to_tsvector를 다시 계산하는 것은 규모를 감당하지 못합니다. 인덱스로
일치를 찾도록 같은 to_tsvector 표현식에 GIN 인덱스를 만드십시오. 인덱싱하는 값이 속성에 대한
표현식이므로 속성 인덱스를 쓰고 표현식을 괄호로 감싸십시오:
CREATE PROPERTY INDEX ON movie
USING gin ((to_tsvector('english', plot::text)));
그러면 검색이 모든 행을 스캔하는 대신 인덱스를 씁니다:
EXPLAIN
MATCH (m:movie)
WHERE to_tsvector('english', m.plot::text) @@ websearch_to_tsquery('english', 'machines')
RETURN m.title;
Bitmap Heap Scan on movie m
Recheck Cond: (to_tsvector('english'::regconfig, (properties.'plot'::text)::text) @@ '''machin'''::tsquery)
-> Bitmap Index Scan on movie_plot_idx
Index Cond: (to_tsvector('english'::regconfig, (properties.'plot'::text)::text) @@ '''machin'''::tsquery)
인덱스가 쓰이려면 쿼리 시점에 쓰는 텍스트 검색 구성(위의 'english')이 인덱스 표현식의 것과
같아야 합니다.
행이 적은 이 예제에서는 순차 스캔이 더 싸므로 플래너가 여전히 그것을 고를 수 있습니다. 인덱스 계획을 보려면
SET enable_seqscan = off를 더하십시오. 실제 말뭉치에서는 플래너가 알아서 인덱스를 고릅니다.
to_tsvector(...)표현식에 대한 GIN 속성 인덱스는 AgensGraph 2.17 이상이 필요합니다.
승격된 컬럼 인덱싱하기#
2.18부터는 속성을 실제 타입 컬럼으로 승격할 수 있으며, 그러면
행마다의 jsonb 읽기와 ::text 형 변환이 사라집니다.
ALTER VLABEL movie ADD COLUMN plot text GENERATED는 컬럼 이름이 가리키는 속성, 여기서는 plot을
비춥니다. 이름이 다른 키를 비추려면 GENERATED (sourceKey)를 씁니다. 쿼리는 컬럼이 아니라 속성의
이름을 계속 댑니다 — 해석은 속성 키로 이루어집니다.
이 페이지의 인덱스는 속성에 대한 표현식이므로 승격할 때 다시 만들어야 합니다. 표현식에 대한 속성
인덱스는 jsonb properties를 상대로 만들어지고 승격된 키에 대한 쿼리는 컬럼을 읽으므로, 둘은 더는 만나지
않게 됩니다.
이것은 컬럼에 대한 SQL로 쓰십시오. \dGi는 여전히 그것을 나열하고, 쿼리도 그것을 씁니다:
DROP PROPERTY INDEX movie_plot_idx;
ALTER VLABEL movie ADD COLUMN plot text GENERATED;
CREATE INDEX movie_plot_idx ON moviekb.movie USING gin (to_tsvector('english', plot));
EXPLAIN (COSTS OFF)
MATCH (m:movie)
WHERE to_tsvector('english', m.plot) @@ websearch_to_tsquery('english', 'machines')
RETURN m.title;
Bitmap Heap Scan on movie m
Recheck Cond: (to_tsvector('english'::regconfig, plot) @@ '''machin'''::tsquery)
-> Bitmap Index Scan on movie_plot_idx
Index Cond: (to_tsvector('english'::regconfig, plot) @@ '''machin'''::tsquery)
이제 m.plot에는 ::text가 필요 없습니다. 승격된 컬럼이 이미 text이기 때문입니다.
표현식 속성 인덱스는 properties에 걸린다를
참고하십시오.
GraphRAG#
전문 검색도 그래프 순회의 시드가 될 수 있습니다. 질문에 텍스트가 가장 잘 맞는 영화를 찾은 다음, 관계를 따라 뻗어 나가 언어 모델에 줄 연결된 맥락을 모읍니다.
벡터 검색의 GraphRAG 예제에 대응하는 어휘 쪽 예제입니다. 임베딩보다 키워드 검색이 잘 다루는 특정 단어(이름·코드·정확한 구절)이 질문에 들어 있을 때 빛을 발합니다.
예제 데이터셋에는 영화 사이의 related_to 간선이 이미
들어 있습니다. 시리즈 속편과 주제로 이어진 작품들입니다. 그래서 아래 순회가 따라갈 것이
있습니다.
Cypher 쿼리 하나가 질문(여기서는 단어 resistance)에 대한 전문 검색 관련도로 영화의 순위를 매기고,
가장 잘 맞는 것을 남긴 뒤 그 이웃으로 뻗어 나갑니다:
MATCH (seed:movie)
WHERE to_tsvector('english', seed.plot::text) @@ websearch_to_tsquery('english', 'resistance')
WITH seed, ts_rank(to_tsvector('english', seed.plot::text),
websearch_to_tsquery('english', 'resistance')) AS rank
ORDER BY rank DESC, seed.title
LIMIT 2
MATCH (seed)-[:related_to]-(ctx:movie)
RETURN seed.title AS seed, collect(DISTINCT ctx.title) AS related_context;
seed | related_context
------------------------------+----------------------------------------------
"Terminator 2: Judgment Day" | ["The Terminator"]
"The Terminator" | ["Terminator 2: Judgment Day", "The Matrix"]
(2 rows)
WHERE 조건과 WITH … ORDER BY rank LIMIT가 쿼리에 텍스트가 가장 잘 맞는 영화를 고릅니다. 그것이
시드이며, 여기서는 Terminator 두 편입니다. 두 번째 MATCH는 related_to 간선을 따라가 관련
작품을 모읍니다.
시드와 그 이웃이 모델에 넘길 맥락을 이룹니다. 순회가 The Matrix까지 닿는 것에 유의하십시오.
키워드 resistance는 그 영화와 일치한 적이 없지만, 기계와의 전쟁이라는 공통 주제로 Terminator
영화들과 이어져 있습니다. 뻗어 나가기 전에 이 어휘 검색을 의미(벡터) 검색과 결합하려면
하이브리드 검색을 참고하십시오.
같은 검색을 손으로 짜는 대신 프레임워크로 하려면 AI·LLM 연동을 참고하십시오.