# PostgreSQL 풀 텍스트 검색: 제대로 하면 빠르다(느리다는 오해 해소)

> Clean Markdown view of GeekNews topic #20247. Use the original source for factual precision when an external source URL is present.

## Metadata

- GeekNews HTML: [https://news.hada.io/topic?id=20247](https://news.hada.io/topic?id=20247)
- GeekNews Markdown: [https://news.hada.io/topic/20247.md](https://news.hada.io/topic/20247.md)
- Type: GN+
- Author: [neo](https://news.hada.io/@neo)
- Published: 2025-04-10T09:50:10+09:00
- Updated: 2025-04-10T09:50:10+09:00
- Original source: [blog.vectorchord.ai](https://blog.vectorchord.ai/postgresql-full-text-search-fast-when-done-right-debunking-the-slow-myth)
- Points: 33
- Comments: 3

## Summary

PostgreSQL의 기본 **Full-Text Search(FTS)** 는 적절한 최적화로 **50배 이상 성능 향상이 가능**합니다. 쿼리 내 to_tsvector() 실행 대신 tsvector 컬럼을 사전 생성하고 GIN 인덱스(fastupdate=off)를 적용하면 검색 속도가 크게 개선됩니다. 테스트에서 최적화 전 41.3초 걸리던 쿼리가 0.88초로 단축되었습니다. ts_rank는 대량 데이터에서 느릴 수 있으나, VectorChord-BM25 확장은 BM25 알고리듬으로 검색 정확도와 속도를 모두 향상시킵니다. 기본 FTS도 올바른 설정으로 충분히 빠르며, 성능 문제는 도구가 아닌 설정에서 비롯될 가능성이 큽니다.

## Topic Body

- PostgreSQL의 기본 Full-Text Search(FTS)는 느리다는 인식이 있지만, **적절한 최적화만 하면 매우 빠르게 동작함**  
- Neon의 블로그에서는 Rust 기반 `pg_search` 확장과 기본 FTS를 비교하여 후자가 느리다고 주장함  
- 하지만 이 비교는 PostgreSQL FTS에 필수적인 **기본 최적화 작업들이 누락**된 상태에서 이루어졌을 가능성이 큼  
- 본 글에서는 **기본 FTS 설정에 단순한 최적화만 적용해도 50배 성능 향상**이 가능함을 수치로 입증함  
  
### 벤치마크 설정 개요  
  
- 1천만 개의 로그 데이터를 가진 테이블을 기반으로 테스트 수행  
  ```sql  
  CREATE TABLE benchmark_logs (  
      id SERIAL PRIMARY KEY,  
      message TEXT,  
      country VARCHAR(255),  
      severity INTEGER,  
      timestamp TIMESTAMP,  
      metadata JSONB  
  );  
  ```  
- 문제의 쿼리 구조:  
  ```sql  
  SELECT country, COUNT(*)  
  FROM benchmark_logs  
  WHERE to_tsvector('english', message) @@ to_tsquery('english', 'research')  
  GROUP BY country  
  ORDER BY country;  
  ```  
  - to_tsvector()를 쿼리 내에서 실행 → 매우 비효율적  
  - GIN 인덱스가 있어도 제대로 활용되지 않음  
  
### 테스트 환경 (기본 설정 복제)  
- EC2 i7ie.xlarge 인스턴스, 로컬 NVMe SSD 사용  
- 4 vCPUs, PostgreSQL 16(Docker) 사용  
- 주요 PostgreSQL 설정:  
  ```bash  
  -c shared_buffers=8GB  
  -c maintenance_work_mem=8GB  
  -c max_parallel_workers=4  
  -c max_worker_processes=4  
  ```  
- 병렬 처리 제한: max_parallel_workers_per_gather = 2 (Neon은 8 사용)  
  
### 성능 저하 요인 1: 실시간 tsvector 계산  
- to_tsvector()를 쿼리 내에서 실행 시:  
- 텍스트 파싱, 형태소 분석 등을 매번 수행  
- 인덱스를 전혀 활용할 수 없음  
- # 해결책: tsvector 컬럼 사전 생성 및 인덱싱  
	- 1\. tsvector 컬럼 추가  
    ```sql  
    ALTER TABLE benchmark_logs ADD COLUMN message_tsvector tsvector;  
    ```  
  - 2\. 데이터 채우기  
    ```sql  
    UPDATE benchmark_logs SET message_tsvector = to_tsvector('english', message);  
    ```  
  -	3\. 인덱스 생성 (fastupdate 비활성화)  
    ```sql  
    CREATE INDEX idx_gin_logs_message_tsvector  
    ON benchmark_logs USING GIN (message_tsvector)  
    WITH (fastupdate = off);  
    ```  
  -	4\. 쿼리 수정  
    ```sql  
    SELECT country, COUNT(*)  
    FROM benchmark_logs  
    WHERE message_tsvector @@ to_tsquery('english', 'research')  
    GROUP BY country  
    ORDER BY country;  
    ```  
### 성능 저하 요인 2: GIN 인덱스 fastupdate=on 설정  
- fastupdate=on은 쓰기 성능엔 유리하지만, 검색 성능에는 악영향  
- 읽기 전용 또는 검색 중심의 데이터셋에는 fastupdate=off가 필수  
- 인덱스가 더 작고 빠르며 pending list 처리 불필요  
- # 최적화된 GIN 인덱스 생성법  
  ```sql  
  CREATE INDEX idx_gin_logs_message_tsvector  
  ON benchmark_logs USING GIN (message_tsvector)  
  WITH (fastupdate = off);  
  ```  
### 성능 향상 수치: 50배 이상 개선  
- 최적화 전: 약 41.3초 (41,301 ms)  
- 최적화 후: 약 0.88초 (877 ms)  
- 약 50배의 성능 향상을 보여줌  
- 병렬 처리 수가 적은 환경에서도 이 성능 달성 가능  
  
### ts_rank 성능은 실제로 느릴 수 있음  
- ts_rank 또는 ts_rank_cd는 모든 결과를 평가한 뒤 정렬하므로 상대적으로 느릴 수 있음  
- 특히 대량 결과를 다룰 때는 CPU/IO 부담이 큼  
  
### 고급 순위 기능: VectorChord-BM25 확장  
- 정렬 정확도 및 속도가 중요한 경우에는 전용 확장 사용이 더 효과적  
- VectorChord-BM25는 PostgreSQL용 확장으로, BM25 알고리즘 기반의 순위 평가 기능 제공  
- Elasticsearch보다 3배 빠름이라는 보고도 있음  
  
### VectorChord-BM25의 장점  
- BM25 알고리즘: TF-IDF보다 발전된 검색 순위 알고리즘  
- 전용 인덱스 형식: Block WeakAnd 등 고속 검색 최적화  
- bm25vector 타입 제공: 토크나이즈된 표현 저장  
- 검색 정확도 및 속도 모두 향상  
  
### 결론: PostgreSQL 기본 FTS도 충분히 빠름  
- tsvector 컬럼과 적절한 GIN 인덱스(fastupdate=off) 사용 시, 기본 FTS로도 매우 빠른 검색 가능  
- 성능 비교는 최적화된 기준으로 이루어져야 함  
- 고급 순위 기능이 필요할 경우엔 VectorChord-BM25와 같은 확장 도구 활용 고려  
- 핵심 메시지: 도구가 느린 것이 아니라, 설정이 문제일 수 있음

## Comments



### Comment 39696

- Author: stadia
- Created: 2025-06-03T18:17:52+09:00
- Points: 1

덕분에 쿼리 튜닝을 했습니다.

### Comment 36992

- Author: pcj9024
- Created: 2025-04-10T12:08:50+09:00
- Points: 1

Hacker News 의견 무섭네영... "천만개? 장난?"

### Comment 36978

- Author: neo
- Created: 2025-04-10T09:50:10+09:00
- Points: 2

###### [Hacker News 의견](https://news.ycombinator.com/item?id=43627646) 
- pg_search의 유지보수자로서, Postgres 문서에 따르면 Neon/ParadeDB 기사와 여기서 사용된 전략 모두 유효한 대안으로 제시됨
  - Postgres FTS의 문제는 단일 쿼리를 최적화하는 것이 아니라 다양한 실제 쿼리에 대해 Elastic 수준의 성능을 제공하는 것임
  - pg_search는 후자의 문제를 해결하기 위해 설계되었으며, 벤치마크도 이를 반영함
  - Neon/ParadeDB 벤치마크는 총 12개의 쿼리를 포함하며, 현실적인 사용 사례에서는 비현실적임
  - pg_search는 다양한 "Elastic 스타일" 쿼리와 Postgres 타입에 대해 간단한 인덱스 정의만으로 작동함

- tsvector를 실시간으로 계산하는 것은 큰 실수임
  - Postgres FTS를 개인 프로젝트에 구현했을 때, 문서를 읽고 지침을 따랐음
  - 문서는 기본 비최적화 사례를 만들고 최적화하는 과정을 명확히 설명함
  - 이 실수를 저지른 사람은 문서를 읽지 않았거나 Postgres FTS를 잘못 표현하려는 의도가 있는 것 같음

- 모든 것을 Postgres에 넣으려는 경향을 이해하지 못하겠음

- Postgres-native의 전체 텍스트 검색 구현을 더 많이 보게 되어 기쁨
  - 대안 솔루션(lucene/tantivy)은 불변 세그먼트에 맞춰 설계되어 Postgres 힙 테이블과 결합하면 더 나쁜 솔루션이 될 수 있음

- 설명 계획이 없어서 무슨 일이 일어나는지 이해하기 어려움
  - 쿼리가 인덱스를 사용하면 실시간 tsvector 재검사는 일치 항목에만 적용되며 벤치마크 쿼리는 LIMIT 10이므로 재검사가 적음
  - 쿼리 조건이 2개의 gin 인덱스에 조건을 가지고 있어, 계획자가 모든 일치 항목을 먼저 재검사하는 것 같음

- 몇 년 전, 네이티브 FTS를 사용하고 싶었으나 실패했음
  - 수천 개의 삽입/초가 있는 테이블에서 전체 업데이트가 느려져 트랜잭션이 시간 초과됨
  - 인덱스를 추가했으나 두 번째 인덱스가 완료되자 시스템에서 시간 초과가 발생함
  - 인덱스를 다시 삭제해야 했고, 실제 FTS 성능을 테스트할 기회를 얻지 못했음

- pg_search와 vchord_bm25 확장 RPM/DEB를 패키징했음
  - 스스로 벤치마크를 하고 싶은 사람들을 위해 링크를 제공함

- 많은 팀이 Elasticsearch나 Meilisearch로 바로 이동하는 것을 보았음
  - 적절히 사용하면 네이티브 PG FTS에서 많은 성능을 얻을 수 있음
  - SQLite + FTS5 + Wasm을 사용하여 브라우저에서 유사한 성능을 얻을 수 있을지 궁금함

- 1천만 개의 레코드는 장난감 데이터셋임
  - 전체 Wikipedia나 2022년 이전 Reddit 댓글과 같은 큰 텍스트 데이터셋이 벤치마크에 더 적합함

- 2008년경 처음으로 pg 전체 텍스트를 사용했음
  - Postgres 전체 텍스트 검색의 문제는 너무 느리다는 것이 아니라 너무 유연하지 않다는 것임
  - 간단한 검색을 추가하는 데는 좋지만 검색을 조정하려면 부족함
  - Solr와 Elasticsearch는 복잡한 인덱스와 검색 처리를 설정할 수 있음
  - Postgres는 이러한 기능을 채택할 수 있지만, 현재는 아무것도 제공하지 않음
  - Postgres는 공백을 기준으로 분할하며, 수동으로 불용어와 어간을 사용할 수 있음
  - 필드 가중치에 기반한 검색 점수 매기기가 불가능함
  - 대안과 비교했을 때 장난감 시스템임
