pgContext: PostgreSQL을 AI 검색 엔진으로 만드는 벡터 및 하이브리드 검색 확장

pgContext 소개

애플리케이션이 이미 PostgreSQL에 데이터를 담고 있어도, 의미 기반 검색을 붙이려면 보통 별도의 벡터 검색 서비스를 하나 더 세우게 됩니다. 이렇게 되면 원본 데이터를 그 서비스로 복사해야 하고, 권한과 백업, 복구 경계가 둘로 나뉘어 서로 동기화 상태를 유지해야 하는 부담이 생깁니다. 데이터가 두 곳에 존재하는 순간 둘 사이의 불일치(drift)와 접근 권한 누락 같은 문제가 따라옵니다.

pgContext는 이 문제를 검색을 데이터 곁에 두는 방식으로 접근합니다. PostgreSQL 17과 18에서 동작하는 확장(extension)으로, 이미 운영 중인 데이터베이스 안에서 밀집 벡터 검색, 메타데이터 필터링을 적용한 근사 검색, 그리고 밀집 벡터와 전문 검색(full-text)을 함께 쓰는 하이브리드 검색을 처리합니다. 벡터와 메타데이터, MVCC 가시성, 접근 제어(ACL)와 행 수준 보안(RLS), 백업과 복제는 모두 평범한 PostgreSQL 테이블이 그대로 관리하는 원본으로 남고, HNSW 같은 가속 구조는 원본의 복사본이 아니라 언제든 다시 만들 수 있는 파생 인덱스 로만 존재합니다. pgContext는 PostgreSQL 확장을 만들어 온 Evokoa가 개발했으며, 같은 팀의 pgGraph와 계보를 공유합니다.

pgContext가 한 확장에 담아 제공하는 기능의 폭은 넓습니다. 정확 검색과 디스크에 저장되는 HNSW 검색, L2와 내적, 코사인, L1 거리 함수, 등록된 컬럼과 JSONB 경로에 대한 필터, 컬렉션과 스크롤, 카운트, 패싯, 그룹핑, 그리고 밀집 벡터와 전문 검색 결과를 합치는 하이브리드 검색이 여기 포함됩니다. 프로젝트는 스스로를 "A full AI search engine, built into Postgres" 라고 소개합니다. 구현 언어는 Rust이며 cargo-pgrx를 기반으로 빌드됩니다. 본 게시물에서는 pgContext의 동작 방식과 메타데이터 필터링, pgvector 대비 성능, 그리고 설치와 사용법을 정리합니다.

pgContext가 검색을 데이터 곁에 두는 방식

pgContext는 애플리케이션이 소유한 테이블과 필터 대상 필드를 확장 카탈로그에 등록하는 것으로 시작합니다. 여기서 정확성의 기준점은 언제나 정확 검색(exact search)입니다. pgcontext_hnsw 접근 방식(access method)은 거리 함수에 묶인 그래프 레코드를 PostgreSQL 인덱스 페이지에 저장하고, 질의가 들어오면 크기가 제한된 후보 집합을 돌려줍니다. 이 후보는 각각 살아 있는 원본 행으로 되돌아가 조회되고, PostgreSQL의 가시성 규칙과 필터를 통과하는지 확인된 뒤, 정확한 점수로 다시 채점되고 나서야 결과로 반환됩니다. 그래서 근사 검색이 빠르게 내놓은 답이라도 최종적으로는 정확하고 권한상 안전한 답이 됩니다. 가속 구조는 언제든 다시 만들 수 있는 인덱스일 뿐이고, 애플리케이션 데이터는 PostgreSQL 밖으로 나가지 않습니다.

아래 그림은 질의가 필터 인지형 HNSW 탐색을 거쳐 정확 재검증으로 이어지는 흐름을 보여줍니다.

pgContext의 메타데이터 필터링

벡터 검색은 필터를 만나면 무너지기 쉽습니다. 근사 검색에 WHERE 절을 덧붙이는 방식은 필터가 선택적일수록 재현율(recall)이 조용히 떨어집니다. pgContext는 필터링을 검색이 끝난 뒤의 후처리가 아니라 검색의 일부로 다룹니다.

등록된 PostgreSQL 컬럼과 JSONB 경로에 대해 Qdrant 스타일의 필터를 쓸 수 있습니다. must / should / must_not, 동등 비교, any / except, 숫자와 날짜 범위, is_null / is_empty 를 지원합니다.

SELECT source_key, score
FROM pgcontext.search(
    'docs', '[ ... ]'::pgcontext.vector,
    '{
       "must":     [{"key": "tenant_id", "match": "acme"},
                    {"key": "price", "range": {"gte": 10, "lt": 20}}],
       "should":   [{"key": "metadata.topic", "match": {"value": "billing"}}],
       "must_not": [{"key": "archived", "match": true}]
     }'::jsonb,
    10
);

핵심 특징은 다음과 같습니다.

  • 별도의 필터 인덱스가 필요 없습니다: 컬럼이나 JSONB 경로를 등록하면 곧바로 필터 대상이 됩니다. 특정 필터를 더 빠르게 만들고 싶을 때 btree 같은 일반 인덱스를 나중에 추가할 수 있지만, 이는 선택적인 최적화이지 전제 조건이 아닙니다.
  • 후처리 필터링이 아니라 필터 인지형 ANN입니다: 선택도(selectivity)가 낮은 구간에서는 정확히 채점하고, 그 이상에서는 하나의 재사용 가능한 마스크를 저장된 HNSW 그래프에 통과시킵니다. 이때 제외된 노드도 탐색 경로를 잇는 연결점 역할을 계속하기 때문에, 필터가 매우 선택적이어도 재현율이 유지됩니다.
  • 검색과 카운트, 패싯이 하나의 문법을 공유합니다: 같은 필터가 검색과 카운트, 패싯 집계를 함께 구동하므로 질의마다 SQL을 손으로 조립할 필요가 없습니다.
  • PostgreSQL이 안전성을 보장합니다: 필터는 등록된 필드에만 바인딩된 파라미터로 묶인 타입 AST로 컴파일되어 SQL 인젝션이나 등록되지 않은 컬럼 변경을 막고, 모든 결과는 MVCC 가시성과 RLS/ACL로 다시 검증됩니다.

전체 문법과 필드 의미는 저장소의 docs/user_guide/filters.md 에 정리되어 있습니다.

pgContext와 pgvector의 성능 비교

저자가 공개한 벤치마크는 표준 GloVe-100-angular 데이터셋(벡터 1,183,514개, 코사인 거리)에서 pgContext와 pgvector를 같은 PostgreSQL 17 컨테이너에 놓고, 같은 병렬 빌드 예산으로 인덱스를 만든 뒤 데이터셋 자체의 정답 이웃(ground-truth)과 비교한 결과입니다. 측정 환경은 Apple M4 Pro(NEON)입니다.

같은 검색 설정마다 pgContext는 pgvector와 동일한 재현율을 맞추면서도 질의당 응답을 3.8배에서 5.3배 빠르게 처리합니다. 예를 들어 ef_search 512에서 recall@10이 0.91일 때 pgContext는 질의당 2.44 ms, pgvector는 12.97 ms가 걸려 5.3배 차이가 납니다. 반대로 읽으면 이 속도는 곧 품질입니다. 질의당 약 2.5 ms의 시간이 주어질 때 pgContext는 recall@10 0.91에 도달하는데, 같은 시간에 pgvector는 0.75에 머뭅니다. 저자는 성숙한 경쟁 엔진인 Qdrant도 함께 비교했으며, Qdrant는 질의별 세그먼트 병렬화 덕분에 매우 높은 재현율 구간에서는 앞선다고 밝히고 있습니다. 모든 수치는 위 환경에서 측정된 자체 벤치마크 결과입니다.

pgContext 설치 및 사용법

pgContext는 pgvector처럼 PostgreSQL이 확장을 인식할 수 있으면 CREATE EXTENSION 한 번으로 활성화됩니다.

CREATE EXTENSION pgcontext;

가장 빠른 설치 방법은 미리 빌드된 Docker 이미지입니다. linux/amd64linux/arm64 를 모두 지원하는 멀티 아키텍처 이미지이고, macOS와 Windows에서도 Docker Desktop의 Linux 컨테이너 지원을 통해 실행할 수 있습니다.

docker pull ghcr.io/evokoa/pgcontext:pg17-v0.2.0
docker run -d --rm \
  --name pgcontext \
  -e POSTGRES_PASSWORD=postgres \
  -e POSTGRES_DB=pgcontext \
  -p 5432:5432 \
  ghcr.io/evokoa/pgcontext:pg17-v0.2.0

소스에서 빌드하려면 Rust 1.96.0, cargo-pgrx 0.19.1, PostgreSQL 17 또는 18과 서버 개발 헤더, 그리고 그에 맞는 pg_config 가 필요합니다. PGXN에서는 pgxn install pgContext 로 설치할 수 있고, macOS용 Homebrew 패키징은 아직 준비 중입니다.

make install PG_CONFIG=/path/to/postgresql-17/bin/pg_config
psql -d postgres -c 'CREATE EXTENSION pgcontext;'

실제 검색까지 이어지는 최소 예시는 다음과 같습니다. 컬렉션을 만들고, 벡터 컬럼과 필터 컬럼을 등록한 뒤, 필터를 건 검색을 실행하는 흐름입니다.

CREATE EXTENSION pgcontext;

CREATE TABLE docs (
    id text PRIMARY KEY,
    embedding pgcontext.vector(3) NOT NULL,
    category text NOT NULL,
    metadata jsonb NOT NULL
);

INSERT INTO docs VALUES
    ('postgres', '[1,0,0]', 'database', '{"language":"sql"}'),
    ('rust', '[0.8,0.2,0]', 'systems', '{"language":"rust"}'),
    ('vectors', '[0.7,0.1,0.2]', 'database', '{"language":"sql"}');

SELECT * FROM pgcontext.create_collection('docs', 'public.docs');
SELECT pgcontext.register_vector('docs', 'embedding', 'embedding', 3, 'cosine');
SELECT pgcontext.register_filter_column('docs', 'category', 'category');
SELECT pgcontext.upsert_points('docs', ARRAY['postgres', 'rust', 'vectors']);

SELECT source_key, score
FROM pgcontext.search(
    'docs', '[1,0,0]'::pgcontext.vector,
    '{"must":[{"key":"category","match":"database"}]}'::jsonb,
    3
);

출력까지 검증된 실행 가능한 형태는 저장소의 playground/demo.sql 에 있습니다. AI 코딩 에이전트로 설치를 자동화한다면, 결정적이고 비대화형인 설치와 검증 절차를 담은 저장소의 AGENTS.md 를 참고할 수 있습니다. Evokoa는 완전 관리형 버전을 Polygres에서 별도로 제공합니다.

pgContext의 라이선스

pgContext는 Apache-2.0 라이선스로 공개되어 있어 개인 및 상업적 목적으로 자유롭게 사용할 수 있습니다.

:framed_picture: pgContext 라이브 데모

:github: pgContext 프로젝트 GitHub 저장소

더 읽어보기




이 글은 GPT 모델로 정리한 글을 바탕으로 한 것으로, 원문의 내용 또는 의도와 다르게 정리된 내용이 있을 수 있습니다. 관심있는 내용이시라면 원문도 함께 참고해주세요! 읽으시면서 어색하거나 잘못된 내용을 발견하시면 덧글로 알려주시기를 부탁드립니다. :hugs:

:pytorch:파이토치 한국 사용자 모임:south_korea:이 정리한 이 글이 유용하셨나요? 회원으로 가입하시면 주요 글들을 이메일:love_letter:로 보내드립니다! 텔레그램(Telegram)이나 Slack/Discord/Teams/Dooray/GoogleChat 등으로도 새 글 알림을 받으실 수 있습니다. :smiley:

:wrapped_gift: 아래:down_right_arrow:쪽에 좋아요:+1:를 눌러주시면 새로운 소식들을 정리하고 공유하는데 힘이 됩니다~ :star_struck: