LabHub

벡터 데이터베이스 — pgvector 와 Qdrant · ANN 인덱스와 재현율 · 실습

ANN 인덱스와 재현율

LabHub 에서 이어서 보기

목표

IVFFlat 과 HNSW 인덱스를 만들어 손잡이(probes·ef_search)를 돌리며 재현율을
재고, 필터가 결과를 줄이는 함정을 재현해 부분 인덱스로 고치고, 전문 검색과 RRF 로
합칩니다. 마지막에 목표 재현율을 만족하는 ef_search 를 데이터베이스 기본값으로
못박습니다.

왜 중요한가

ANN 인덱스는 정확도를 팔아 속도를 삽니다. 얼마나 팔았는지는 재현율로만 보이고,
재현율은 재지 않으면 아무도 모릅니다. 검색이 느려지면 신고가 들어오지만 검색이
틀려지면 아무도 신고하지 않습니다. 그래서 이 실습은 인덱스를 만드는 명령보다
인덱스를 재는 일에 시간을 씁니다.

환경

이 파드는 postgres 계정으로 돕니다. psql 만 치면 labdb 에 붙습니다.

export PATH=/usr/lib/postgresql/16/bin:$PATHexport PGHOST=127.0.0.1 PGUSER=lab PGDATABASE=labdb

세션마다 데이터베이스가 새로 뜨므로 1편의 표는 없습니다.
psql -f /opt/lab/fixtures/vector/load.sqlarticles 를 만들고 임베딩까지 적재합니다(전문 검색용 tsv 생성 열도 함께 둡니다 — 7단계에서 씁니다).

산출물은 전부 /root/ann/ 아래에 둡니다. mkdir -p /root/ann 를 먼저 하세요.

단계

1. psql -f /opt/lab/fixtures/vector/load.sqlarticles 를 적재하고, 질의 "파드가 OOMKilled 로 계속 재시작한다" 의 상위 10건을 explain (analyze, buffers) 로 실행해 /root/ann/01-baseline.txtrows=·exact_ms= 와 계획 전문을 남깁니다.
2. articles_embedding_ivf 라는 IVFFlat 인덱스를 vector_cosine_opslists = 40 으로 만들고, ivfflat.probes 를 1·10·40 으로 바꾸며 recall@10 을 재어 /root/ann/02-ivfflat.txtlists=·probes_1=·probes_10=·probes_40= 을 남깁니다.
3. IVFFlat 인덱스를 지우고 articles_embedding_hnsw 라는 HNSW 인덱스를 vector_cosine_ops 로, m = 16·ef_construction = 64 로 만든 뒤 /root/ann/03-hnsw.txtm=·ef_construction=·index_bytes=(pg_relation_size) 를 남깁니다.
4. recall.pyhnsw.ef_search 를 10·40·100 으로 바꾸며 recall@10 을 재어 /root/ann/04-recall.txtef_10=·ef_40=·ef_100= 을 남깁니다.
5. 같은 질의("파드가 OOMKilled 로 계속 재시작한다")를 인덱스로 한 번·enable_indexscan = off 로 한 번 explain (analyze, buffers) 해서 /root/ann/05-explain.txtindex_ms=·seq_ms= 와 두 계획 전문을 남깁니다.
6. lang = 'en' 필터로 ef_search 40 에서 재현율과 평균 반환 행 수를 재어 함정을 재현하고, ef_search 400 으로 다시 잰 뒤, where lang = 'en' 인 부분 HNSW 인덱스 articles_embedding_en_hnsw 를 만들어 ef_search 100 으로 다시 잽니다. /root/ann/06-filter.txtrows_before=·recall_before=·recall_ef400=·recall_partial= 을 남깁니다.
7. articles.tsv(load.sql 이 만든 to_tsvector('simple', title || ' ' || body) 생성 열)에 GIN 인덱스 articles_tsv_gin 을 만들고, 질의 "커넥션 풀 고갈로 pgbouncer 가 대기한다" 의 벡터 상위 20건과 plainto_tsquery('simple', 'pgbouncer') 의 전문 검색 상위 20건(ts_rank 내림차순, 동점은 id 오름차순)을 RRF(k=60)로 합쳐 상위 10건을 /root/ann/07-hybrid.txtid|score|title 로 남깁니다.
8. hnsw.ef_search 를 10·20·…·100 순으로 올리며 recall@10 이 0.95 이상이 되는 가장 작은 값을 찾아 alter database labdb set hnsw.ef_search = <값> 으로 데이터베이스 기본값으로 걸고, /root/ann/08-tune.txtef_search=·recall= 을 남깁니다.

참고

단계 8개

  1. 적재와 정확 검색 기준선
  2. IVFFlat — 첫 근사 인덱스
  3. HNSW 인덱스로 바꾼다
  4. ef_search 와 재현율
  5. 플래너가 인덱스를 쓰는가
  6. 필터 함정과 부분 인덱스
  7. tsvector 와 RRF 하이브리드
  8. 목표 재현율의 ef_search 를 기본값으로