PostgreSQL 전문 검색 성능 최적화
Emily Parker
Product Engineer · Leapcell

소개
오늘날 데이터 중심 애플리케이션에서는 빠르고 정확한 검색이 무엇보다 중요합니다. 전자 상거래 플랫폼에서 제품을 검색하든, 문서 관리 시스템에서 파일을 검색하든, 지식 기반에서 기사를 제공하든, 사용자는 즉각적이고 관련성 높은 결과를 기대합니다. PostgreSQL의 내장 전문 검색(FTS) 기능은 이러한 요구 사항에 대한 강력한 솔루션을 제공하여 개발자가 비정형 텍스트를 효율적으로 쿼리할 수 있도록 합니다. 하지만 적절한 최적화 없이는 데이터 볼륨이 증가함에 따라 FTS 성능이 크게 저하될 수 있습니다. 이 글에서는 인덱싱 전략, 사전의 역할, 정교한 랭킹 알고리즘에 초점을 맞춰 PostgreSQL 전문 검색을 최적화하는 필수 기술을 살펴보고 애플리케이션이 원활한 검색 경험을 제공하도록 보장합니다.
핵심 개념 이해
최적화에 대해 자세히 알아보기 전에, 최적화할 PostgreSQL 전문 검색의 기본 구성 요소를 이해하는 것이 중요합니다.
- 어근(Lexemes) 및 토큰(Tokens): 텍스트를 PostgreSQL의 FTS에 입력하면 먼저 "토큰"(개별 단어 또는 기호)으로 분해됩니다. 그런 다음 이러한 토큰은 "어근"을 생성하도록 처리되는데, 이는 토큰의 정규화된 형태이며 변형(예: "running", "ran", "runs"는 모두 어근 "run"으로 축소될 수 있음)이 제거된 것입니다. 이러한 정규화는 텍스트 검색 구성을 통해 달성됩니다.
- 텍스트 검색 구성(Text Search Configuration): 문서가 전문 검색을 위해 어떻게 처리되는지를 정의하는 파서, 사전 및 템플릿의 모음입니다. 토큰이 어떻게 추출되고, 어떤 토큰이 무시되는지(불용어), 그리고 토큰이 어떻게 어근으로 정규화되는지를 결정합니다.
tsvector
: 문서의 고유 어근 목록과 해당 위치를 저장하는 PostgreSQL의 특수 데이터 유형입니다. FTS의 핵심 인덱싱 단위입니다.tsquery
: 검색 쿼리를 나타내는 데이터 유형으로, 어근으로 처리되지만 검색 논리를 정의하는 연산자(AND, OR, NOT)도 포함됩니다.- GIN 인덱스(Generalized Inverted Index):
tsvector
열에 사용되는 기본 인덱스 유형입니다. GIN 인덱스는 각 어근에 대한 문서 ID 목록을 저장하여 특정 어근을 포함하는 문서를 매우 빠르게 조회할 수 있습니다.
전문 검색 최적화
FTS 성능 최적화는 주로 문서 처리 효율성과 검색 쿼리 실행 속도 향상에 중점을 둡니다.
GIN을 사용한 효율적인 인덱싱
전문 검색에 대한 가장 중요한 성능 향상은 올바른 인덱스를 사용하는 데서 비롯됩니다. tsvector
열의 경우 GIN 인덱스가 표준 선택입니다.
tsvector
열 및 GIN 인덱스 생성:
먼저 tsvector
데이터를 저장할 열이 필요합니다. 소스 텍스트가 변경될 때마다 이 tsvector
가 자동으로 업데이트되는 생성된 열이나 트리거를 갖는 것이 종종 유익합니다.
ALTER TABLE products ADD COLUMN search_vector tsvector GENERATED ALWAYS AS ( to_tsvector('english', coalesce(name, '') || ' ' || coalesce(description, '')) ) STORED; CREATE INDEX product_search_idx ON products USING GIN (search_vector);
이 예에서 search_vector
는 name
및 description
필드를 결합하여 english
tsvector
로 변환하는 생성된 열입니다. STORED
키워드는 이 열이 계산되고 저장됨을 의미하며, 인덱싱에 중요합니다. 그런 다음 이 search_vector
열에 GIN 인덱스가 생성됩니다.
올바른 텍스트 검색 구성 선택:
english
구성은 일반적인 용도로 좋은 선택이지만, 특정 언어 또는 도메인의 경우 사용자 지정 구성이 필요할 수 있습니다. 구성은 파서, 템플릿 및 사전을 정의합니다.
사전 활용을 통한 어근 처리 개선
사전은 원시 토큰을 의미 있는 어근으로 변환하는 데 중요합니다. 불용어, 동의어 및 어간 추출을 처리합니다.
불용어: "a", "the", "is", "are"와 같은 단어는 일반적으로 검색에 관련이 없습니다. "불용어 사전"이 이러한 단어를 제거합니다. PostgreSQL의 내장 구성에는 일반적으로 하나가 포함되어 있습니다.
어간 추출: 단어를 기본 형태로 축소합니다(예: "running"을 "run"으로). 이를 통해 "run"을 검색하면 "running", "ran", "runs" 등이 포함된 문서가 찾아집니다. PostgreSQL은 이를 위해 ispell
또는 snowball
사전을 사용합니다.
동의어: 사용자 지정 동의어 사전을 정의하여 유사한 용어를 매핑할 수 있습니다. 예를 들어 "car"를 "automobile"에 매핑하면 "car" 검색 시 "automobile"도 찾아집니다.
사용자 지정 사전 생성:
-
사전 템플릿 정의:
CREATE TEXT SEARCH DICTIONARY my_synonym_dict ( TEMPLATE = synonym, SYNONYMS = 'my_synonyms.txt' );
이는 PostgreSQL 데이터 디렉터리(또는 구성된 경로)에
my_synonyms.txt
라는 파일이 있으며 동의어 매핑이 포함되어 있다고 가정합니다. 형식은 일반적으로word : synonym
과 같습니다. -
텍스트 검색 구성 업데이트:
ALTER TEXT SEARCH CONFIGURATION english ALTER MAPPING FOR asciiword, hword_asciipart, hword_numpart, numword WITH my_synonym_dict, english_stem;
이는
english_stem
사전 앞에my_synonym_dict
를 추가하여 일부 토큰 유형에 적용됩니다. 이는 동의어가 어간 추출 전에 적용됨을 의미합니다. 구성에서 사전의 순서는 중요합니다.
랭킹 알고리즘을 사용한 검색 결과 개선
빠른 검색에도 불구하고 사용자에게는 관련성 높은 결과가 필요합니다. 랭킹 알고리즘은 쿼리에 비해 문서의 중요도를 기준으로 결과를 정렬하는 데 도움이 됩니다.
ts_rank
및 ts_rank_cd
:
PostgreSQL은 순위를 계산하기 위해 ts_rank
및 ts_rank_cd
함수를 제공합니다. ts_rank
는 문서에서 쿼리 용어의 빈도를 고려하는 반면, ts_rank_cd
(Cover Density)는 용어의 근접성도 고려합니다. 일반적으로 ts_rank_cd
는 다중 단어 쿼리에 대해 더 나은 결과를 생성합니다.
SELECT title, ts_rank_cd(search_vector, websearch_to_tsquery('english', 'PostgreSQL performance optimization')) AS rank FROM articles WHERE search_vector @@ websearch_to_tsquery('english', 'PostgreSQL performance optimization') ORDER BY rank DESC LIMIT 10;
여기서 websearch_to_tsquery
는 보다 사용자 친화적인 쿼리 구문 분석에 사용됩니다. ts_rank_cd
함수는 search_vector
가 쿼리와 얼마나 잘 일치하는지를 기반으로 순위를 계산합니다.
벡터 가중치 지정:
tsvector
의 다른 부분에 가중치를 할당하여 특정 필드에 더 많은 중요도를 부여할 수 있습니다. 예를 들어, title
에서의 일치는 body
에서의 일치보다 더 중요할 수 있습니다.
ALTER TABLE products ADD COLUMN weighted_search_vector tsvector GENERATED ALWAYS AS ( setweight(to_tsvector('english', coalesce(name, '')), 'A') || ' ' setweight(to_tsvector('english', coalesce(description, '')), 'B') ) STORED; -- 그런 다음 이를 랭킹에 사용: SELECT name, ts_rank_cd(weighted_search_vector, websearch_to_tsquery('english', 'portable speaker')) AS rank FROM products WHERE weighted_search_vector @@ websearch_to_tsquery('english', 'portable speaker') ORDER BY rank DESC LIMIT 10;
가중치 A, B, C, D는 기본적으로 1.0, 0.4, 0.2, 0.1 승수에 해당하지만, weights
배열을 제공하여 ts_rank
함수에서 사용자 지정할 수 있습니다.
실제 적용: 제품 카탈로그 검색
사용자가 이름과 설명으로 항목을 검색하는 제품 카탈로그를 고려해 봅시다. 제품 이름의 검색에 더 높은 관련성을 부여하고 싶습니다.
스키마:
CREATE TABLE products ( id SERIAL PRIMARY KEY, name TEXT NOT NULL, description TEXT, price NUMERIC );
최적화된 FTS 설정:
-- 1. 가중치가 지정된 tsvector 열 추가 ALTER TABLE products ADD COLUMN search_document tsvector GENERATED ALWAYS AS ( setweight(to_tsvector('english', name), 'A') || ' ' setweight(to_tsvector('english', coalesce(description, '')), 'B') ) STORED; -- 2. 가중치가 지정된 tsvector에 GIN 인덱스 생성 CREATE INDEX products_search_idx ON products USING GIN (search_document); -- 3. 랭킹이 포함된 예제 검색 쿼리 SELECT id, name, description, ts_rank_cd(search_document, websearch_to_tsquery('english', 'wireless earbuds')) AS rank FROM products WHERE search_document @@ websearch_to_tsquery('english', 'wireless earbuds') ORDER BY rank DESC LIMIT 20;
이 설정은 GIN 인덱스로 인해 검색이 빠르고, 제품 이름(가중치 'A')의 결과가 설명(가중치 'B')보다 우선 순위가 지정됨을 보장합니다.
결론
PostgreSQL 전문 검색 최적화는 인덱싱 전략 계획, 텍스트 처리를 위한 지능적인 사전 사용, 랭킹 알고리즘의 신중한 적용을 포함하는 다각적인 프로세스입니다. GIN 인덱스를 효과적으로 구현하고, 적절한 사전을 사용하여 텍스트 검색 구성을 사용자 지정하고, 가중치가 지정된 tsvector
로 ts_rank_cd
를 활용함으로써 검색 결과의 속도와 관련성 모두를 크게 향상시킬 수 있습니다. 이러한 기술은 애플리케이션이 강력하고 응답성이 뛰어난 검색 경험을 제공하여 원시 데이터를 사용자에게 실행 가능한 통찰력으로 전환하도록 보장합니다.