반응형
반응형

[DataBase] Postgres를 검색엔진으로 활용하기  

 

https://anyblockers.com/posts/postgres-as-a-search-engine

 

Postgres as a search engine

Build a retrieval system with semantic, full-text, and fuzzy search in Postgres to be used as a backbone in RAG pipelines.

anyblockers.com

 

 

 


  • Postgres 내에서 시맨틱, 전문, 퍼지 검색을 모두 갖춘 하이브리드 검색 엔진을 구축할 수 있음
  • 검색은 많은 앱에서 중요한 부분이지만 제대로 구현하기 쉽지 않음. 특히 RAG 파이프라인에서는 검색 품질이 전체 프로세스의 성패를 좌우할 수 있음
  • 의미론적(Semantic) 검색이 트렌디하지만, 전통적인 어휘 기반 검색은 여전히 검색의 중추임
  • 의미론적 기술은 결과를 개선할 수 있지만, 견고한 텍스트 기반 검색의 기반 위에서 가장 잘 작동함

Postgres를 활용한 검색 엔진 구현하기

  • 세 가지 기술을 결합:
    • tsvector를 사용한 전체 텍스트 검색
    • pgvector를 사용한 의미론적 검색
    • pg_trgm을 사용한 퍼지 매칭
  • 이 접근 방식은 모든 상황에서 절대적으로 최고는 아닐 수 있지만, 별도의 검색 서비스를 구축하는 것에 대한 훌륭한 대안임
  • 기존 Postgres 데이터베이스 내에서 구현하고 확장할 수 있는 견고한 출발점
  • Postgres를 모든 것에 사용해야 하는 이유 : 그냥 Postgres를 모든 곳에 사용하세요, PostgreSQL로 충분하다, 그냥 Postgres 쓰세요

FTS와 의미론적 검색 구현

  • Supabase에 하이브리드 검색 구현에 대한 훌륭한 문서가 있으므로, 이를 시작점으로 삼을 것임
  • 가이드에 따라 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
  • 또 다른 방법은 OpenAI의 GPT를 사용하여 자체적으로 구축하는 것
  • 크로스 인코더는 쿼리와 문서 간의 관계를 더 잘 이해할 수 있도록 하여 검색 결과의 정확도를 높일 수 있음
  • 그러나 계산 비용이 크기 때문에 확장성 면에서는 제한이 있음
  • 따라서 초기 검색에는 양방향 인코더를 사용하고, 검색된 소수의 문서에 대해서만 크로스 인코더를 적용하는 두 단계 접근 방식이 효과적임

언제 대안 솔루션을 찾아야 할까

  • PostgreSQL은 많은 검색 시나리오에 적합한 선택이지만 한계가 없는 것은 아님
  • BM25와 같은 고급 알고리듬의 부재는 다양한 문서 길이를 처리할 때 느껴질 수 있음
  • PostgreSQL의 전체 텍스트 검색은 TF-IDF에 의존하므로 매우 긴 문서와 대규모 컬렉션의 희귀 용어에 어려움을 겪을 수 있음
  • 대안 솔루션을 찾기 전에 반드시 측정해 보아야 함. 그럴 가치가 없을 수도 있음

결론

  • 이 글에서는 기본적인 전체 텍스트 검색부터 퍼지 매칭, 의미론적 검색, 결과 부스팅과 같은 고급 기술까지 많은 내용을 다루었음
  • Postgres의 강력한 기능을 활용하여 특정 요구사항에 맞춘 강력하고 유연한 검색 엔진을 만들 수 있음
  • Postgres는 검색을 위해 가장 먼저 떠오르는 도구는 아니지만 정말 멀리 갈 수 있게 해줌
  • 훌륭한 검색 경험을 위한 핵심
    • 지속적인 반복과 미세 조정
    • 논의한 디버깅 기법을 사용하여 검색 성능을 이해하고, 사용자 피드백과 행동을 기반으로 가중치와 매개변수를 조정하는 것을 두려워하지 말아야 함
  • PostgreSQL은 고급 검색 기능이 부족할 수 있지만, 대부분의 경우 충분히 강력한 검색 엔진을 구축할 수 있음
  • 대안 솔루션을 찾기 전에 먼저 Postgres의 기능을 최대한 활용하고 성능을 측정해 보는 것이 좋고, 그래도 부족하다면 그때 다른 솔루션을 고려해 볼 수 있음

https://news.hada.io/topic?id=16468&utm_source=weekly&utm_medium=email&utm_campaign=202436

 

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

Postgres 내에서 시맨틱, 전문, 퍼지 검색을 모두 갖춘 하이브리드 검색 엔진을 구축할 수 있음검색은 많은 앱에서 중요한 부분이지만 제대로 구현하기 쉽지 않음. 특히 RAG 파이프라인에서는 검색

news.hada.io

 

 

반응형
반응형

▶ 데이터 타입

SQL Server PostgreSQL
BIGINT 64-bit integer BIGINT
BINARY(n) Fixed-length byte string BYTEA
BIT 1, 0 or NULL BOOLEAN
CHAR(n), CHARACTER(n) Fixed-length character string, 1 ⇐ n ⇐ 8000 CHAR(n), CHARACTER(n)
DATE Date (year, month and day) DATE
DATETIME Date and time with fraction TIMESTAMP(3)
DATETIME2(p) Date and time with fraction TIMESTAMP(p)
DATETIMEOFFSET(p) Date and time with fraction and time zone TIMESTAMP(p) WITH TIME ZONE
DECIMAL(p,s), DEC(p,s) Fixed-point number DECIMAL(p,s), DEC(p,s)
DOUBLE PRECISION Double-precision floating-point number DOUBLE PRECISION
FLOAT(p) Floating-point number DOUBLE PRECISION
IMAGE Variable-length binary data, ⇐ 2G BYTEA
INT, INTEGER 32-bit integer INT, INTEGER
MONEY 64-bit currency amount MONEY
NCHAR(n) Fixed-length Unicode UCS-2 string CHAR(n)
NTEXT Variable-length Unicode UCS-2 data, ⇐ 2G TEXT
NUMERIC(p,s) Fixed-point number NUMERIC(p,s)
NVARCHAR(n) Variable-length Unicode UCS-2 string VARCHAR(n)
NVARCHAR(max) Variable-length Unicode UCS-2 data, ⇐ 2G TEXT
REAL Single-precision floating-point number REAL
ROWVERSION Automatically updated binary data BYTEA
SMALLDATETIME Date and time TIMESTAMP(0)
SMALLINT 16-bit integer SMALLINT
SMALLMONEY 32-bit currency amount MONEY
TEXT Variable-length character data, ⇐ 2G TEXT
TIME(p) Time (hour, minute, second and fraction) TIME(p)
TIMESTAMP Automatically updated binary data BYTEA
TINYINT 8-bit unsigned integer, 0 to 255 SMALLINT
UNIQUEIDENTIFIER 16-byte GUID (UUID) data CHAR(16)
VARBINARY(n) Variable-length byte string, 1 ⇐ n ⇐ 8000 BYTEA
VARBINARY(max) Variable-length binary data, ⇐ 2G BYTEA
VARCHAR(n) Variable-length character string, 1 ⇐ n ⇐ 8000 VARCHAR(n)
VARCHAR(max) Variable-length character data, ⇐ 2G TEXT
XML XML data XML

▶ 함수

SQL Server PostgreSQL
DATEADD Add an interval to datetime INTERVAL expression
ISNULL(exp, replacement) Replace NULL with the specified value COALESCE(exp, replacement)



출처: https://icodebroker.tistory.com/6429 [ICODEBROKER]

 

반응형
반응형

자동 증가 형식 (serial 등)

 

PosgtreSQL에서 사용할 수 있는 데이터 형에서 자동 증가 타입의 사용법에 대해 설명하겠다. 자동 증가 타입으로 설정한 컬럼은 자동으로 연속 값이 저장된다. 자동 증가 타입은 smallserial, serial, bigserial의 3 가지 유형의 데이터가 존재한다.

자동 증가 형식

연번 형은 취급 숫자의 범위가 다른 3 가지 데이터 유형이 있다.

형식크기범위별칭

smallserial 2 바이트 1~32767 serial2
serial 4 바이트 1~2147483647 serial4
bigserial 8 바이트 1~9223372036854775807 serial8

자동 증가 타입이 설정된 컬럼이 포함된 테이블에 데이터를 추가를 하면, 자동 증가 타입의 컬럼에 직접 값을 지정하는 것이 아니라 기본값이 포함되도록 한다. 그러면 자동으로 지금까지 등록된 값보다 큰 값(일반적으로 1 큰 값)이 자동으로 저장된다. (MySQL에서 말하는 컬럼에 AUTO_INCREMENT를 설정 한 것과 비슷하다)

※ 자동 증가 형식은 내부적으로 시퀀스를 이용하여 구현되어 있다.


자동 증가 타입이 설정된 컬럼에 값을 지정하여 데이터를 추가

자동 증가 타입이 설정된 컬럼에 따로 지정하지 않으면 디폴트 값으로 자동으로 연속적인 값이 저장되지만, 임의의 값을 지정하여 데이터를 추가 할 수도 있다.

현재 4개의 데이터를 추가한 상태에 다음 데이터를 추가하게 되면, 자동 증가 타입이 설정된 id 컬럼에 다음 디폴트 값인 5가 저장된다.

여기서 id 컬럼에 값을 지정하여 데이터를 추가 할 수 있다.

 

mydb=# insert into myfriends values (7, 'Yunjo', 'Paris');
INSERT 0 1
mydb=# select * from myfriends;
 id |  name   | address
----+---------+---------
  1 | Yunho   | Goyang
  2 | Seonah  | Bucheon
  3 | Yongtae | Seoul
  4 | Dongeog | Gangnam
  7 | Younjo  | Paris
(5개 행)

-- 데이터를 추가한 후 테이블에서 데이터를 검색해 보면 지정한 값이 그대로 저장되어 있다. 
   이와 같이 자동 증가 타입이 설정된 컬럼에도 값을 지정하여 데이터를 추가 할 수 있다.
-- 여기에서 다시 id 컬럼에 지정하지 않고 디폴트값으로 데이터를 추가하면, 
   id 컬럼에 무슨 값이 들어가는지 확인하려고 한다.

mydb=# insert into myfriends (name, address) values ('Sueun', 'Yongin');
INSERT 0 1
mydb=# select * from myfriends;
 id |  name   | address
----+---------+---------
  1 | Yunho   | Goyang
  2 | Seonah  | Bucheon
  3 | Yongtae | Seoul
  4 | Dongeog | Gangnam
  7 | Younjo  | Paris
  5 | Sueun   | Yongin
(6개 행)

-- 데이터를 추가한 후에 테이블에서 데이터를 검색해 보면, 
   id 컬럼은 원래 다음으로 들어가려던 5가 저장된다.
-- 그럼, id 컬럼에 디폴트 값이 포함되도록 2개의 데이터를 더 추가해 보자.

mydb=# insert into myfriends (name, address) values ('Hansol', 'Seocho'), ('Yujin', 'Unknown');
INSERT 0 2
mydb=# select * from myfriends;
 id |  name   | address
----+---------+---------
  1 | Yunho   | Goyang
  2 | Seonah  | Bucheon
  3 | Yongtae | Seoul
  4 | Dongeog | Gangnam
  7 | Younjo  | Paris
  5 | Sueun   | Yongin
  6 | Hansol  | Seocho
  7 | Yujin   | Unknown
(8개 행)

-- id 컬럼에는 이전 저장된 값 5 다음으로 6과 7이 저장되어 있다. 
   이미 id 컬럼에 7이라는 값이 저장된 데이터를 수동으로 추가되었지만, 
   중복된 값이 있는지와는 상관없이 연속적인 값이 저장이 되었다.
-- 이와 같이 자동 증가 타입이 설정된 컬럼에 값을 지정하여 데이터를 추가 할 수도 있지만, 
   그 데이터는 자동으로 저장되는 값으로 반영되지 않는다는 점을 주의가 필요하다.


.

 

* http://www.devkuma.com/books/pages/1444 

반응형
반응형

PostgreSQL - windows 설치

 

https://www.enterprisedb.com/downloads/postgres-postgresql-downloads

 

Download PostgreSQL Database for Windows, Linux and MacOS & 32-bit or 64-bit Versions | EDB

Download PostgreSQL packages or installers free from EDB. Get PostgreSQL for Windows, Linux and MacOS platforms. Download 32-bit or 64-bit versions. Download open-source PostgreSQL now.

www.enterprisedb.com

반응형
반응형

[PostgreSQL] 행 순서(ROW NUMBER)에 조건 적용하기

SELECT *
  FROM (SELECT ROW_NUMBER() OVER (ORDER BY DATE) AS ROW, *
          FROM TEST_TABLE LIMIT 10) T;
          
          
          
          
-- 짝수번째 행만 조회하는 쿼리
SELECT *
  FROM (SELECT ROW_NUMBER() OVER (ORDER BY DATE) AS ROW, *
          FROM TEST_TABLE LIMIT 10) T
  WHERE ROW%2 = 0;

 

SELECT
	product_id,
	product_name,
	group_id,
	ROW_NUMBER () OVER (ORDER BY product_id)
FROM
	products;
반응형
반응형

[PostgresSQL] 컬럼 내 특정 문자로 split 후 값 출력

    사용법은 split_part('원래 문자열', '자를 문자', 위치) 로 사용한다. 

postgresql 에서 문자열 또는 필드를 붙일 경우는 concat 함수를 사용한다.

  • 문자열 합치기: '||' 연산자
  • 문자열 합치기: CONCAT() 함수
  • 구분자를 포함하여 문자열 합치기: CONCAT_WS() 함수


 

 

-- split_part(컬럼,자르고자하는 문자, 인덱스)


-- ex
select split_part(tel,'-',4)
from test
-- test 테이블의 tel 컬럼에서 - 문자로 자른 후 4번째에 있는 데이터를 출력 (index는 0이 아닌 1부터 시작)




update 테이블이름 set 
  year = split_part(date::TEXT, '-', 1)::SMALLINT
, month=split_part(date::TEXT, '-', 2)::SMALLINT

-- concat 

SELECT employee_id,first_name,last_name,
concat(first_name,'-',last_name) "Name of the Employee" 
FROM employees
WHERE department_id=100;

SELECT	*
	, col_1 || ', ' || col_2 AS db_name
	, id || ' : ' || col_2 AS db_id
FROM char_type_test;

-- (3) CONCAT_WS : CONCAT With Separator
-- : CONCAT_WS(separator, str_1, str_2, ...);

SELECT 	*
	, CONCAT_WS(', ', col_1, col_2) AS db_name_3
	, CONCAT_WS(' : ', id, col_2) AS db_id_3
FROM char_type_test;
반응형

+ Recent posts