LabHub

벡터 데이터베이스 — pgvector 와 Qdrant · 벡터 검색의 원리 · 이론

거리가 곧 뜻이다 — 벡터 검색의 원리

LabHub 에서 이어서 보기

한 줄 요약

벡터 검색은 "같은 낱말이 있는가" 가 아니라 "뜻이 가까운가" 를 거리로 재는 검색이다. 데이터베이스가 할 일은 벡터를 만드는 것이 아니라, 만들어진 벡터를 저장하고, 거리를 재고, 조건과 함께 빨리 찾는 것이다.

왜 이게 필요했나

장애 티켓 2천 건이 쌓여 있다. 새 티켓 "파드가 OOMKilled 로 계속 재시작한다" 와 비슷한 과거 사례를 찾고 싶다. LIKE '%OOMKilled%' 는 그 낱말이 없는 "메모리 한도 때문에 컨테이너가 죽는다" 를 못 찾고, 영어로 적힌 "pod restart after memory limit" 은 더더욱 못 찾는다. 전문 검색(tsvector)도 결국 낱말 일치라 같은 벽에 부딪힌다.

선수 과목의 임베딩이 이 벽을 넘는다. 문장을 64차원 좌표로 바꾸면 뜻이 비슷한 문장은 가까운 자리에 놓인다. 남는 문제는 데이터베이스 쪽이다 — 좌표 2천 개, 곧 2백만 개를 어디에 두고, 어떻게 재고, 조건과 어떻게 합치는가.

어떻게 동작하나

pgvector 는 PostgreSQL 에 vector(n) 형과 거리 연산자 세 개를 더한다.

| 연산자 | 이름 | 값이 작을수록 |
| --- | --- | --- |
| <-> | L2(유클리드) 거리 | 가깝다 |
| <=> | 코사인 거리 (1 − 코사인 유사도) | 가깝다 |
| <#> | 음의 내적 (−a·b) | 가깝다 |

셋 다 "작을수록 가깝다" 로 통일한 이유는 ORDER BY ... LIMIT k 한 줄로 k-최근접(kNN)을 적기 위해서다. 내적은 클수록 가까우므로 부호를 뒤집어 놓았다.

벡터의 길이를 1 로 맞춰(정규화) 두면 셋은 같은 순위를 준다. L2 거리의 제곱이 2 × 코사인 거리이고, 내적이 코사인 유사도 그 자체가 되기 때문이다. 이 코스의 임베딩은 전부 정규화돼 있어서 어느 연산자를 쓰든 결과가 같다. 정규화되지 않은 벡터에서는 셋이 다른 답을 내므로, 모델이 어떤 거리로 학습됐는지를 먼저 확인해야 한다.

ORDER BY embedding <=> q LIMIT 10 을 인덱스 없이 실행하면 2천 행을 전부 읽어 거리를 재고 정렬한다. 이것이 정확 검색(exact kNN) 이고, 이 코스에서 모든 근사 검색의 정답 기준이 된다. 2천 건이면 1밀리초대라 그냥 써도 된다 — 인덱스는 다음 모듈의 일이다.

조건은 그냥 WHERE 로 붙인다. 정확 검색에서는 조건에 맞는 행만 읽고 그 안에서 정렬하므로 아무 함정이 없다.

실무에서 진짜 중요한 것

첫째, 질의 벡터와 문서 벡터는 같은 상자에서 나와야 한다. 다른 모델의 좌표는 다른 공간이라 거리에 아무 뜻이 없다. 모델을 바꾸면 기존 벡터 전부가 무효이고, 모델 버전을 표에 함께 적어 두는 습관이 그 사고를 막는다.

둘째, 차원을 열에 못박는다. vector(64) 로 선언하면 길이가 다른 벡터가 들어올 때 DB 가 거부한다. vector 로 열어 두면 조용히 섞여 들어가고, 거리 계산이 실행 시점에 터진다.

셋째, 정확 검색을 먼저 돌려 본다. 결과가 이상하면 원인은 대개 인덱스가 아니라 임베딩이다. 정확 검색의 기준이 있어야 뒤에 인덱스와 벡터 중 어느 쪽이 틀렸는지를 가를 수 있다.

다음 실습에서 할 것

vector(64) 열이 있는 표에 2천 건을 적재하고, 세 연산자가 같은 순위를 주는지 확인한 뒤, embed.py 로 만든 질의 벡터로 정확 검색과 범주 필터를 결합한다.