# Postgres를 검색엔진으로 활용하기

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

## Metadata

- GeekNews HTML: [https://news.hada.io/topic?id=16468](https://news.hada.io/topic?id=16468)
- GeekNews Markdown: [https://news.hada.io/topic/16468.md](https://news.hada.io/topic/16468.md)
- Type: news
- Author: [xguru](https://news.hada.io/@xguru)
- Published: 2024-08-26T10:07:02+09:00
- Updated: 2024-08-26T10:07:02+09:00
- Original source: [anyblockers.com](https://anyblockers.com/posts/postgres-as-a-search-engine)
- Points: 28
- Comments: 2

## Summary

Postgres를 활용하여 시맨틱, 전문, 퍼지 검색을 모두 갖춘 하이브리드 검색 엔진을 구축할 수 있으며, 이는 별도의 검색 서비스를 구축하는 것에 대한 훌륭한 대안입니다. FTS와 의미론적 검색을 구현하고, `pg_trgm` 확장을 사용하여 퍼지 검색을 추가함으로써 검색 품질을 크게 향상시킬 수 있습니다. Postgres의 강력한 기능을 활용하여 특정 요구사항에 맞춘 유연한 검색 엔진을 만들 수 있으며, 지속적인 반복과 미세 조정을 통해 검색 성능을 최적화할 수 있습니다.

## Topic Body

- Postgres 내에서 시맨틱, 전문, 퍼지 검색을 모두 갖춘 하이브리드 검색 엔진을 구축할 수 있음   
- 검색은 많은 앱에서 중요한 부분이지만 제대로 구현하기 쉽지 않음. 특히 RAG 파이프라인에서는 검색 품질이 전체 프로세스의 성패를 좌우할 수 있음  
- 의미론적(Semantic) 검색이 트렌디하지만, 전통적인 어휘 기반 검색은 여전히 검색의 중추임  
- 의미론적 기술은 결과를 개선할 수 있지만, 견고한 텍스트 기반 검색의 기반 위에서 가장 잘 작동함  
  
### Postgres를 활용한 검색 엔진 구현하기   
- 세 가지 기술을 결합:  
  - `tsvector`를 사용한 전체 텍스트 검색  
  - `pgvector`를 사용한 의미론적 검색  
  - `pg_trgm`을 사용한 퍼지 매칭  
- 이 접근 방식은 모든 상황에서 절대적으로 최고는 아닐 수 있지만, 별도의 검색 서비스를 구축하는 것에 대한 훌륭한 대안임  
- 기존 Postgres 데이터베이스 내에서 구현하고 확장할 수 있는 견고한 출발점  
- Postgres를 모든 것에 사용해야 하는 이유 : [그냥 Postgres를 모든 곳에 사용하세요](https://news.hada.io/topic?id=8018), [PostgreSQL로 충분하다](https://news.hada.io/topic?id=13231), [그냥 Postgres 쓰세요 ](https://news.hada.io/topic?id=16365)  
  
### FTS와 의미론적 검색 구현  
- Supabase에 [하이브리드 검색 구현에 대한 훌륭한 문서](https://supabase.com/docs/guides/ai/hybrid-search)가 있으므로, 이를 시작점으로 삼을 것임  
- 가이드에 따라 GIN 인덱스를 사용하여 FTS를 구현하고, pgvector(bi-encoder dense retrieval이라고도 함)를 사용하여 의미론적 검색을 구현함  
- 개인적인 경험으로는 1536차원의 임베딩을 선택하는 것이 훨씬 더 나은 결과를 얻을 수 있음  
- Supabase 함수를 CTE와 쿼리로 대체하고, 매개변수 앞에 `$`를 붙임.   
- 여기서는 RRF(Reciprocal Ranked Fusion)를 사용하여 결과를 병합함  
- 이 방법은 여러 목록에서 높은 순위를 차지하는 항목이 최종 목록에서 높은 순위를 부여받도록 보장함  
- 또한 일부 목록에서는 높은 순위지만 다른 목록에서는 낮은 순위인 항목이 최종 목록에서 높은 순위를 부여받지 않도록 보장함  
- 순위를 분모에 넣어 점수를 계산하면 순위가 낮은 레코드에 불이익을 줄 수 있음  
- 주목할 만한 사항  
  - `$rrf_k`: 첫 번째 순위 항목의 점수가 극단적으로 높아지는 것을 방지하기 위해(순위로 나누기 때문에), 분모에 k 상수를 추가하여 점수를 평활화하는 경우가 많음  
  - `$ _weight`: 각 방법에 가중치를 할당할 수 있음. 이는 결과를 조정할 때 매우 유용함  
  
### 퍼지 검색 구현하기  
- 이전까지의 방법으로도 많은 부분을 해결할 수 있지만, 명명된 엔티티에서 오타가 있을 경우 즉각적인 이슈가 발생할 수 있음  
- 의미론적 검색은 유사성을 포착하여 이러한 이슈 중 일부를 제거하지만, 이름, 약어 및 의미론적으로 유사하지 않은 기타 텍스트에 대해서는 어려움을 겪음  
- 이를 완화하기 위해 `pg_trgm` 확장을 도입하여 퍼지 검색을 허용  
  - 트라이그램으로 작동함. 트라이그램은 단어를 3자 시퀀스로 분해하기 때문에 퍼지 검색에 유용  
  - 이를 통해 오타나 약간의 변형이 포함되어 있더라도 유사한 단어를 매칭할 수 있음  
  - 예를 들어 "hello"와 "helo"는 많은 트라이그램을 공유하므로 퍼지 검색에서 더 쉽게 매칭될 수 있음  
- 원하는 열에 대해 새 인덱스를 생성하고, 그 후 전체 검색 쿼리에 추가  
- pg_trgm 확장은 % 연산자를 노출하여 유사도가 `pg_trgm.similarity_threshold`(기본값은 0.3)보다 큰 텍스트를 필터링함  
- 유용한 다른 여러 연산자도 있음.   
  
### 전문 검색 튜닝하기   
- tsvector 가중치 조정하기 :실제 문서에는 제목뿐만 아니라 내용도 포함됨   
- 열이 여러 개 있어도 임베딩 열은 하나만 유지함  
- 개인적으로 여러 임베딩을 유지하는 것보다 `title`과 `body`를 같은 임베딩에 유지하는 것이 성능에 큰 차이가 없다는 것을 발견함  
- 결국 `title`은 본문의 간단한 표현이어야 함. 필요에 따라 이를 실험해 보는 것이 좋음  
- title은 짧고 키워드가 풍부할 것으로 예상되는 반면, body는 더 길고 더 많은 세부 정보를 포함할 것임   
- 따라서 전체 텍스트 검색 열이 서로 어떻게 가중치를 부여하는지 조정해야 함  
- 문서에서 단어가 있는 위치나 중요도에 따라 우선순위를 부여할 수 있음  
  - A-weight: 가장 중요(예: 제목, 헤더). 기본값 `1.0`  
  - B-weight: 중요(예: 문서 시작 부분, 요약). 기본값 `0.4`  
  - C-weight: 표준 중요도(예: 본문 텍스트). 기본값 `0.2`  
  - D-weight: 가장 덜 중요(예: 각주, 주석). 기본값 `0.1`  
- 문서 구조와 애플리케이션 요구사항에 따라 가중치를 조정하여 관련성을 미세 조정함  
- 제목에 더 많은 가중치를 부여하는 이유  
  - 제목은 일반적으로 문서의 주요 주제를 간결하게 표현하기 때문  
  - 사용자는 검색할 때 먼저 제목을 훑어보는 경향이 있으므로 제목의 키워드 일치는 일반적으로 본문 텍스트의 일치보다 사용자의 의도와 더 관련이 있음  
  
### 길이에 따른 조정  
- `ts_rank_cd` 문서를 읽어보면 정규화 매개변수가 있다는 것을 알 수 있음.   
  - > 두 랭킹 함수 모두 문서의 길이가 순위에 어떤 영향을 미쳐야 하는지 여부를 지정하는 정수 `normalization` 옵션을 사용함. 정수 옵션은 여러 동작을 제어하므로 비트 마스크임: `|`를 사용하여 하나 이상의 동작을 지정할 수 있음(예: `2|4`).  
- 이러한 다양한 옵션을 사용하여 다음을 수행 가능   
  - 문서 길이 편향 조정  
  - 다양한 문서 집합에서 관련성 균형 조정  
  - 일관된 표현을 위해 랭킹 결과 조정  
- 제목에는 `0`(정규화 없음), 본문에는 `1`(로그 문서 길이)을 설정하면 좋은 결과를 얻을 수 있음  
- 다시 말하지만, 사용 사례에 가장 적합한 옵션을 찾기 위해 다양한 옵션을 실험해 보는 것이 좋음  
  
### 크로스 인코더를 사용한 재랭킹  
  
- 많은 검색 시스템은 두 단계로 구성됨  
- 즉, 양방향 인코더를 사용하여 초기 N개의 결과를 검색한 다음, 크로스 인코더를 사용하여 이러한 결과를 검색 쿼리와 비교하여 순위를 매김  
  - 양방향 인코더(bi-encoder) : 빠르기 때문에 많은 수의 문서를 검색하는 데 좋음   
  - 크로스 인코더(cross-encoder)   
    - 더 느리지만 성능이 더 좋아 검색된 결과의 순위를 다시 매기는 데 좋음  
    - 쿼리와 문서를 함께 처리하여 둘 사이의 관계에 대한 더 미묘한 이해를 가능하게 함  
    - 이는 계산 시간과 확장성을 희생하면서 더 나은 랭킹 정확도를 제공함  
- 이를 수행하기 위한 다양한 도구들이 있음  
- 가장 좋은 것 중 하나는 [Cohere의 Rerank](https://cohere.com/blog/rerank-3)임   
- 또 다른 방법은 [OpenAI의 GPT를 사용하여 자체적으로 구축하는 것](https://cookbook.openai.com/examples/search_reranking_with_cross-encoders)임  
- 크로스 인코더는 쿼리와 문서 간의 관계를 더 잘 이해할 수 있도록 하여 검색 결과의 정확도를 높일 수 있음  
- 그러나 계산 비용이 크기 때문에 확장성 면에서는 제한이 있음  
- 따라서 초기 검색에는 양방향 인코더를 사용하고, 검색된 소수의 문서에 대해서만 크로스 인코더를 적용하는 두 단계 접근 방식이 효과적임  
  
### 언제 대안 솔루션을 찾아야 할까  
  
- PostgreSQL은 많은 검색 시나리오에 적합한 선택이지만 한계가 없는 것은 아님  
- BM25와 같은 고급 알고리듬의 부재는 다양한 문서 길이를 처리할 때 느껴질 수 있음  
- PostgreSQL의 전체 텍스트 검색은 TF-IDF에 의존하므로 매우 긴 문서와 대규모 컬렉션의 희귀 용어에 어려움을 겪을 수 있음  
- 대안 솔루션을 찾기 전에 반드시 측정해 보아야 함. 그럴 가치가 없을 수도 있음  
  
### 결론  
  
- 이 글에서는 기본적인 전체 텍스트 검색부터 퍼지 매칭, 의미론적 검색, 결과 부스팅과 같은 고급 기술까지 많은 내용을 다루었음  
- Postgres의 강력한 기능을 활용하여 특정 요구사항에 맞춘 강력하고 유연한 검색 엔진을 만들 수 있음  
- Postgres는 검색을 위해 가장 먼저 떠오르는 도구는 아니지만 정말 멀리 갈 수 있게 해줌  
- 훌륭한 검색 경험을 위한 핵심  
  - 지속적인 반복과 미세 조정  
  - 논의한 디버깅 기법을 사용하여 검색 성능을 이해하고, 사용자 피드백과 행동을 기반으로 가중치와 매개변수를 조정하는 것을 두려워하지 말아야 함  
- PostgreSQL은 고급 검색 기능이 부족할 수 있지만, 대부분의 경우 충분히 강력한 검색 엔진을 구축할 수 있음  
- 대안 솔루션을 찾기 전에 먼저 Postgres의 기능을 최대한 활용하고 성능을 측정해 보는 것이 좋고, 그래도 부족하다면 그때 다른 솔루션을 고려해 볼 수 있음

## Comments



### Comment 28357

- Author: eajrezz
- Created: 2024-08-27T16:41:01+09:00
- Points: 2

한글 검색도 잘되는지 궁금하네요.

### Comment 28285

- Author: xguru
- Created: 2024-08-26T10:08:01+09:00
- Points: 1

오늘 위클리 주제도 Postgres 였는데, 역시나 또 Postgres네요. 확실히 인기에 비례해서 글이 많이 나오는듯 ㅎㅎ  
BM25 는 아래를 참고 하세요   
  
[pg_bm25 - Postgres에서 Elastic 수준의 품질을 제공하는 Full-Text 검색 확장 ](https://news.hada.io/topic?id=11287)  
[ParadeDB - PostgreSQL for Search](https://news.hada.io/topic?id=12779)
