[전자책] 벡터 데이터베이스 - SQLite, PostgreSQL로 직접 만드는 시맨틱 검색과 RAG
니틴 보르완카르 지음, 정영균 옮김 / 한빛미디어 / 2026년 8월
평점 :
장바구니담기


한빛미디어 서평단 <나는리뷰어다> 활동을 위해서 책을 협찬받아 작성된 서평입니다.


지식이 농담이 된 시대에 벡터 스토어의 미시적 구성을 보다


AI(Artificial Intelligence, 인공지능)에 낯선 개념을 물으면 설명이 쏟아지고, 예제 코드까지 금세 따라온다. 시간을 들여 익힌 지식도 질문 한 번이면 얻을 수 있는 것처럼 느껴지는 시대다. 그런데 설명을 읽고 고개를 끄덕이는 일과, 검색 결과가 어긋났을 때 원인을 짚는 일 사이에는 여전히 거리가 있다. 비슷한 문서를 왜 놓쳤는지 판단하려면 도구 아래의 원리를 알아야 한다.


Spring AI와 Gemini, PostgreSQL의 pgvector 조합으로 RAG 강의를 진행한 뒤에도 검색 계층의 내부가 궁금했다. 『벡터 데이터베이스』는 연결해 사용하던 검색 기능을 거리 계산과 인덱스, 저장과 조회의 단위로 다시 들여다볼 기회가 됐다. SQLite와 PostgreSQL이라는 구체적인 구현을 따라가며 원리를 확인할 수 있다는 점이 특히 좋았다.


RAG(Retrieval-Augmented Generation, 검색 증강 생성)는 관련 자료를 먼저 찾아 근거로 붙여 주고, 대규모 언어 모델(LLM, Large Language Model)이 그 근거를 바탕으로 답하게 하는 방식이다. 사내 문서를 넣어 두고 “우리 규정상 이게 되나요?”라고 묻는 챗봇을 떠올리면 이해가 빠르다. 문서를 검색하기 좋게 나눈 조각을 ‘청크’, 그 조각의 의미를 숫자 목록으로 표현한 것을 ‘임베딩’이라고 한다. 벡터 스토어는 이 임베딩을 담아 두고 질문과 가까운 것을 찾아 주는 계층이다.


이번 독서에서는 ‘가까운 것을 찾는다’는 짧은 설명 안에 어떤 선택들이 숨어 있는지 따라갔다. 비슷함의 기준을 정하고, 후보를 빠르게 찾고, 데이터베이스에서 조건에 맞게 꺼내는 일은 어떻게 연결될까. 검색할 자료를 준비하는 과정까지 함께 보니, 따로 알고 있던 개념들이 하나의 검색 흐름으로 이어졌다.


앞서 말한 도구를 짧게 정리하면 이렇다. Spring AI는 여러 AI 모델과 벡터 저장소를 같은 방식으로 다루게 해 주는 프레임워크, Gemini는 Google의 생성형 AI 모델, PostgreSQL은 널리 쓰이는 오픈소스 관계형 데이터베이스이고, pgvector는 PostgreSQL에 벡터 검색을 더하는 확장이다.


트랜스포머로 딥러닝은 끝났다고요? 임베딩은 이제부터 시작입니다


트랜스포머가 나온 뒤로 딥러닝의 큰 문제는 다 정리된 것처럼 이야기되곤 한다. 하지만 임베딩은 트랜스포머 이후에 처음 생긴 개념이 아니다. 단어와 문서를 벡터로 바꾸던 방법이 먼저 있었고, 트랜스포머는 그 표현을 문맥에 따라 더 정교하게 만드는 쪽으로 이어졌다.


​


word2vec(word to vector)은 단어를, doc2vec(document to vector)은 문서를 벡터로 바꾸는 초기 방법이다. “왕 - 남자 + 여자 ≈ 여왕”처럼 단어 사이의 의미 관계가 숫자 계산으로 드러나는 게 word2vec의 대표적인 예다. 트랜스포머는 지금의 LLM을 있게 한 신경망 구조로, 문맥 속 단어들 사이의 관계를 반영해 표현을 만들어 낸다.


사람의 말을 컴퓨터가 다루는 자연어 처리(NLP, Natural Language Processing)를 공부하며 봤던 이름들이라 반가웠다. word2vec에서 배웠던 벡터 연산이 지금의 검색 결과를 설명하는 언어로 돌아온 셈이다. 예전에 공부한 내용이 현재의 RAG를 이해하는 바탕이 되고, 자연어 처리와 데이터베이스 지식이 연결되는 대목이 오래 남았다.




임베딩은 비정형 데이터를 숫자 벡터로 옮겨, 의미가 비슷한 것끼리 가까이 놓는 일이다. 벡터는 데이터를 표현한 숫자 목록이고, 차원은 그 목록에 들어 있는 숫자의 개수다. 개별 숫자가 곧 특정 단어나 개념 하나에 대응하는 것은 아니다. 문맥을 얼마나 잘 반영해 이 숫자들을 만드는지가 검색 품질을 좌우한다. 트랜스포머가 임베딩을 없앤 게 아니라 더 쓸 만하게 만든 셈이고, 그래서 임베딩은 지금 시스템에서 오히려 더 중요해졌다.


여기서 한 걸음 더 들어가면 ‘의미가 비슷하다’는 말을 어떻게 계산으로 옮길지가 남는다. 가령 “비밀번호를 잊었어요”라는 질문과 “계정 암호 재설정 방법”이라는 문서는 표현이 달라도 관련될 수 있다. 이런 관계를 찾는 것이 의미 유사도 검색이다. 다만 데이터베이스가 문장을 직접 이해하는 것은 아니다. 임베딩 모델이 만든 표현을 바탕으로 숫자 사이의 가까움을 계산하고, 그것을 의미적 관련성의 단서로 사용하는 것이다.


대표적인 기준이 코사인 유사도와 L2 거리다. 코사인 유사도는 두 벡터가 얼마나 같은 방향을 향하는지 본다. 길이의 영향을 제거하고 각도를 비교하므로, 같은 방향이면 1이고 값이 클수록 방향이 비슷하다. L2 거리(Euclidean distance, 유클리드 거리)는 두 점 사이의 직선거리를 고차원으로 확장한 것이다. 좌표의 차이를 비교하며 값이 작을수록 가깝다.


실제 임베딩이 아닌 간단한 숫자 예로 (1, 0)과 (2, 0)을 보면 차이가 선명하다. 방향은 같아서 코사인 유사도는 1이지만, 두 점 사이의 L2 거리는 1이다. ‘비슷하다’는 기준이 방향인지 위치인지에 따라 계산 결과가 달라지는 셈이다.


비교하는 벡터들의 길이를 모두 1로 맞추는 정규화를 거치면 코사인과 L2의 가까운 순서는 같아지지만, 점수 자체가 같아지는 것은 아니다. 따라서 자연어 검색이라고 무조건 한 척도를 고르기보다, 임베딩 모델이 어떤 척도와 정규화를 전제로 하는지 함께 봐야 한다. 거리 척도에 관한 기술 설명도 이 관계를 정리해 준다.


점수의 방향도 구분해야 한다. 코사인 거리는 흔히 1 - 코사인 유사도로 계산하므로 작을수록 가깝다. 유사도가 0.8이라고 해서 ‘정답일 확률 80%’라는 뜻도 아니다. 의미가 가까운 문서가 사실이 틀리거나 질문의 조건과 맞지 않을 수도 있다. 임베딩을 이해한다는 것은 숫자를 만드는 방법뿐 아니라, 그 숫자를 어떤 기준으로 비교하고 어디까지 믿을지 이해하는 일이었다.


흩뿌려진 노드를 재빨라진 위계로 (HNSW)


코사인이나 L2가 ‘가깝다’의 기준이라면, HNSW(Hierarchical Navigable Small World)는 그 기준에 맞는 후보를 빠르게 찾는 방법이다. 둘은 경쟁하는 기술이 아니라 함께 쓰는 요소다. ‘위계’라는 말이 어렵게 들리면 그래프의 기본부터 떠올리면 된다. 노드는 그래프의 점, 간선은 점 사이의 연결이다. 벡터 검색에서는 각 벡터가 노드가 된다. 차원과 데이터 규모가 커질수록 질문 벡터와 모든 벡터의 거리를 다 계산하는 부담이 커지고, 그래서 비슷한 후보만 빠르게 골라내는 근사 최근접 이웃(ANN, Approximate Nearest Neighbor)을 쓴다. 정확한 최근접 결과의 보장을 완화해 속도를 얻는 절충이다.


인덱스는 검색을 빠르게 하도록 미리 만든 자료 구조다. HNSW는 이 ANN을 구현하는 그래프 기반 인덱스로, 노드를 여러 층의 그래프에 걸쳐 배치한다. 일부 노드는 여러 층에 함께 나타나고, 위층은 성글고 아래층으로 갈수록 촘촘하다. 위층에서 대략 위치를 잡고 아래층으로 내려가며 후보를 좁히는, 성긴 지도에서 점점 확대해 가는 탐색에 가깝다. 여기서 위계는 개념의 상하 분류나 언어의 위계가 아니라, 연결을 성글게·촘촘하게 나눠 둔 탐색 구조일 뿐이다.



이때 품질을 재는 잣대가 ‘재현율’이다. 찾아야 할 관련 결과 중 실제로 찾아낸 비율로, 근사 검색이 정확 검색의 상위 결과를 얼마나 회수하는지로 예를 들 수 있다. 검색이 빠른 대신 메모리 사용량이 늘고 인덱스 구축 시간이 더 걸릴 수 있다는 점도 함께 기억에 남았다. 데이터 분포와 탐색 설정에 따라 속도와 검색 품질이 달라진다.


여러 층을 오가며 후보를 좁히는 구조를 이해하고 나니, HNSW라는 이름이 단순히 켜 두는 인덱스 옵션보다 구체적으로 다가왔다. 얼마나 찾아볼지에 따라 무엇을 얻고 놓치는지 생각하게 된 것이다. 검색 속도와 결과의 품질을 함께 판단할 수 있는 관점을 얻었다는 점에서 특히 흥미로운 부분이었다.


정확성을 높이기 위한 킥 한 스푼 - 메타데이터와 SQL


유사도 계산과 인덱스를 실제 서비스로 가져오려면, 원문과 벡터를 어디에 저장하고 어떤 조건으로 조회할지 결정해야 한다. 이 지점에서 sqlite-vss와 pgvector가 단순한 도구 이름을 넘어 구체적인 구현으로 다가왔다.


SQLite는 파일 기반의 경량 관계형 데이터베이스다. 여기에 sqlite-vss(SQLite Vector Similarity Search) 확장을 붙이면 FAISS(Facebook AI Similarity Search)라는 벡터 유사도 검색 라이브러리의 기능을 활용할 수 있다. pgvector는 PostgreSQL에 벡터 타입과 거리 연산, HNSW 같은 인덱스를 더하는 확장이다. 데이터베이스 본체, 확장, 검색 라이브러리는 서로 다른 역할이며, pgvector가 FAISS 위에서 동작하는 구조는 아니다.




두 구성 모두 별도 벡터 전용 서버를 세우지 않고 기존 데이터와 벡터를 같은 데이터베이스에서 다룰 수 있다. 이때 사용하는 SQL(Structured Query Language, 구조화 질의 언어)은 데이터를 저장하고 조회하는 명령을 표현하는 언어다. 익숙한 데이터베이스에 벡터 타입과 검색 기능이 더해지는 과정을 보니, 벡터 스토어가 실제로 무엇을 저장하고 어떤 연산을 수행하는지 한층 선명해졌다.


실제 쿼리에서도 앞서 본 거리 척도의 차이가 드러난다. pgvector의 <-> 연산자는 L2 거리, <=>는 코사인 거리를 구하며, 두 경우 모두 작은 값부터 정렬하면 가까운 후보가 먼저 나온다. HNSW 인덱스도 사용할 척도에 맞춰 만든다. 수학적인 ‘가까움’의 정의가 조회 명령으로 이어지는 부분이다.


구현을 비교할 때는 반환값도 살펴야 한다. FAISS의 L2 검색은 제곱근을 취하지 않은 제곱 L2 거리를 반환한다. 가까운 순서는 같더라도 점수를 다른 구현과 그대로 비교할 수는 없다.


이제 조회 조건을 더해 볼 차례다. 현재 규정을 물었는데 폐기된 규정이 검색되면 주제는 맞아도 답의 근거로는 부적합하다. 메타데이터는 작성자·날짜·분류처럼 문서에 붙이는 부가 정보다. SQL로 ‘분류가 규정이고 현재 유효한 문서’라는 조건을 함께 표현하면, 벡터의 가까움만으로 판단하던 검색에 업무 맥락을 더할 수 있다.


다만 조건을 더한 뒤에도 결과를 점검해야 한다. pgvector의 근사 인덱스 검색에서는 후보를 탐색한 뒤 필터가 적용되어 필요한 결과 수를 채우지 못할 수도 있다. 거리 척도, 탐색 범위, 필터 조건을 함께 봐야 하는 이유다.


여기에 문서 전체의 단어를 분석하는 전문 검색을 결합하는 방식이 하이브리드 검색이다. 표현이 다른 문서를 찾는 데는 의미 검색이 유용하고, 제품명이나 오류 코드처럼 표기 자체가 중요한 경우에는 키워드 기반 검색이나 정확 일치 조건이 필요하다. 두 검색의 점수 체계가 다를 수 있으므로 결과를 합칠 때도 순위나 점수를 조정해야 한다. 메타데이터 필터는 대상의 조건을 정하고, 하이브리드 검색은 서로 다른 검색 결과를 결합한다는 차이가 있다.




sqlite-vss와 pgvector를 따라가며 좋았던 점은 이런 판단을 저장과 조회의 형태로 확인할 수 있다는 것이었다. 의미가 가까운 후보를 찾는 일과, 답변의 근거로 쓸 수 있는 문서를 고르는 일 사이에 어떤 조건이 필요한지 구체적으로 보였다.


참고로 sqlite-vss 공식 저장소는 활발한 개발이 중단된 상태이며 개발자가 sqlite-vec에 주력한다고 안내한다(2026-09-27 확인). 책의 예제로 구조를 이해하는 일과 새 프로젝트의 도구를 선택하는 일은 구분할 필요가 있다.


에이전틱 시대에는 결국 아키텍처만 남는다


여기까지는 준비된 벡터를 어떻게 비교하고 찾는지 살펴봤다. 그렇다면 검색할 벡터는 어떻게 준비되는가. 거리 척도와 인덱스를 이해한 뒤에는, 그 앞단의 데이터 준비 과정이 검색 품질에 미치는 영향도 더 구체적으로 보였다.


ETL(Extract, Transform, Load)은 데이터를 추출하고, 변환하고, 저장하는 흐름을 말한다. 벡터 검색에서는 PDF(Portable Document Format) 등 문서 파일에서 텍스트를 꺼내고, 불필요한 내용을 정리하고, 청크로 나누고, 출처·날짜 같은 메타데이터를 붙인 뒤, 임베딩을 만들어 저장하는 과정으로 구체화된다.


문서 수집 → 텍스트 추출·정리 → 청킹 → 임베딩 생성 → 원문·메타데이터·벡터 저장


흐름만 보면 단순하지만 결정할 것은 많다. PDF에서 본문이 제대로 추출되지 않으면 뒤에서 검색을 잘해도 쓸 만한 근거가 나오지 않는다. 청크가 너무 짧으면 문맥이 끊기고, 너무 길면 질문과 무관한 내용까지 섞인다. 경계 부분을 겹쳐 나누는 방법도 있지만 중복과 저장량을 함께 고려해야 한다. 임베딩 모델을 바꿀 때 기존 벡터와 새 벡터를 같은 기준으로 비교할 수 있는지도 확인해야 한다. 저장소에 넣기 전에 이미 검색 품질을 좌우하는 선택이 여러 번 일어나는 셈이다.


자료를 넣는 ETL과 질문을 처리하는 검색 흐름도 구분해서 볼 수 있었다. 앞단에서 자료와 인덱스를 준비했다면, 질문이 들어온 뒤에는 문서와 비교 가능한 방식으로 질문을 임베딩하고, 가까운 후보를 찾고, 조건과 순위를 조정해 LLM에 전달한다. 검색 인덱스만 바꿔서 해결할 문제인지, 애초에 문서를 잘못 잘랐는지, 임베딩 표현이 질문과 맞지 않는지 따로 살펴야 한다. 코사인 유사도·HNSW·sqlite-vss·pgvector가 이 흐름 속에서 각자 어느 일을 맡는지 보이기 시작했다.

​

강의에서 써 온 Spring AI도 문서를 읽고 변환하고 저장하는 구성 요소로 ETL 흐름을 제공한다. 책의 Python 예제와 구현 언어는 달라도 살펴야 할 단계가 이어진다는 점이 반가웠다. 코드 예시를 따라가는 것뿐 아니라, 각 단계가 맡은 역할을 중심으로 읽어도 배울 부분이 많았다.


Python 패키지와 로컬 컴퓨터에서 LLM을 실행하는 Ollama를 중심으로 구현을 따라가는 구성은 내부 흐름을 확인하는 데 도움이 된다. 다만 이를 에이전트 프레임워크나 관리형 모델 서비스에 연결하는 예시가 더 있었다면, 익힌 구조를 다른 환경으로 옮기기에도 좋았겠다. AI 모델과 도구를 연결하는 LangChain, 실행 순서와 상태를 관리하는 LangGraph 같은 선택지와 비교해 보고 싶은 아쉬움이 남았다.


에이전틱(agentic)은 AI가 도구를 골라 여러 단계의 작업을 수행하는 방식을 뜻한다. 에이전트가 검색을 대신 호출해도, 잘못 추출한 문서나 부적절한 거리 척도까지 저절로 해결되지는 않는다. 무엇을 저장하고, 무엇을 비슷하다고 판단하고, 어떤 근거를 답변에 넘길지는 여전히 설계의 몫이다. 그래서 ‘아키텍처만 남는다’는 말은 각 단계의 책임과 데이터 흐름을 이해해야 도구를 바꿔도 판단할 수 있다는 뜻에 가깝다.


RAG 파이프라인을 한 번 구성해 보고, 이제는 검색 결과가 왜 그렇게 나오는지 이해하고 싶은 개발자에게 특히 권하고 싶다. 코사인 유사도와 L2로 비교 기준을 이해하고, HNSW로 탐색 방식을 살핀 뒤, sqlite-vss와 pgvector의 저장·조회 구조와 ETL까지 연결해 읽을 수 있다. 학습 자료나 사내 문서 검색을 만들려는 사람, 검색 원리를 설명해야 하는 강사에게도 유용한 관점이다.


자격증 공부와 학습 자료 정리에도 이런 벡터 검색 기법을 더 잘 활용해 보고 싶어졌다. 자료를 어떻게 나누고, 무엇을 비슷하다고 판단하며, 어떤 조건으로 꺼낼지부터 생각하게 된다는 점이 이번 독서에서 얻은 가장 큰 변화다. 도구가 바뀌어도 이런 질문들은 남는다.


#한빛미디어 #나는리뷰어다 #벡터데이터베이스 #RAG #임베딩 #벡터검색 #시맨틱검색 #pgvector #생성형AI #코사인유사도 #의미유사도검색 #HNSW #postgres #sqlite #faiss


댓글(0) 먼댓글(0) 좋아요(0)
좋아요
l 공유하기 l 북마크하기찜하기 lthankstoThanksTo