PostgreSQL에서 벡터 데이터베이스를 구축하려는 개발자들에게 희소식입니다. Langchain-postgres 0.0.16 버전이 나오면서 임베딩 저장과 검색이 훨씬 간편해졌습니다. 특히 기존 PGVector에서 PGVectorStore로 업그레이드되면서 성능과 관리 편의성이 크게 개선되었는데요, 오늘은 이 최신 모듈을 활용해서 실제로 어떻게 임베딩을 저장하고 검색하는지 핵심 기능을 중심으로 알아보겠습니다.
핵심 요약
Langchain-postgres 0.0.16은 PostgreSQL 기반 벡터 데이터베이스를 쉽게 구축할 수 있게 해주는 최신 패키지입니다. PGVectorStore를 통해 문서를 임베딩으로 변환하여 저장하고, 유사도 검색으로 관련 데이터를 빠르게 찾을 수 있습니다. 비동기 처리를 지원하며 psycopg3 드라이버를 사용해 안정적인 연결 관리가 가능합니다.
왜 지금 Langchain-postgres인가
요즘 AI 애플리케이션을 만들 때 가장 고민되는 부분이 뭘까요? 바로 대량의 텍스트 데이터를 어떻게 효율적으로 저장하고 검색할지입니다. 단순히 키워드 매칭만으로는 한계가 있거든요. 사용자가 “강아지 키우는 법”을 검색했을 때 “반려견 육아 가이드”도 함께 보여줄 수 있어야 하잖아요. 이럴 때 필요한 게 바로 벡터 검색입니다.
Langchain-postgres는 PostgreSQL의 pgvector 확장을 활용해서 이런 시맨틱 검색을 구현할 수 있게 해줍니다. 그리고 0.0.16 버전부터는 PGVectorStore라는 개선된 모듈이 도입되면서 이전보다 훨씬 사용하기 편해졌습니다. 기존에 PGVector를 쓰던 분들도 있으실 텐데, 공식적으로 deprecated 되었으니 새로운 PGVectorStore로 마이그레이션하는 걸 권장합니다.
데이터베이스 연결의 핵심, PGEngine
모든 시작은 데이터베이스 연결부터입니다. Langchain-postgres는 PGEngine이라는 클래스를 통해 PostgreSQL과의 연결을 관리합니다. 이게 정말 편한 게, 연결 풀링을 자동으로 처리해주거든요.
연결 방법은 크게 두 가지입니다. 첫 번째는 연결 문자열을 직접 입력하는 방식입니다. postgresql+asyncpg 드라이버를 사용하는데, 여기서 주목할 점은 비동기 처리를 기본으로 한다는 겁니다. 대용량 데이터를 다룰 때 비동기는 선택이 아니라 필수죠. 연결 문자열에 사용자명, 비밀번호, 호스트, 포트, 데이터베이스 이름을 넣어주면 PGEngine.from_connection_string 메서드로 엔진 객체를 생성할 수 있습니다.
두 번째 방법은 SQLAlchemy의 엔진을 먼저 만들고, 그걸 PGEngine으로 감싸는 방식입니다. 이미 SQLAlchemy를 사용하고 있는 프로젝트라면 이 방법이 더 자연스럽겠죠. create_async_engine으로 엔진을 만든 다음 PGEngine.from_engine 메서드에 전달하면 됩니다.
한 가지 팁을 드리자면, psycopg3를 사용하고 싶다면 연결 문자열에서 postgresql+psycopg로 설정하면 됩니다. 드라이버 이름은 psycopg지만 실제로는 psycopg3가 동작하니까 헷갈리지 마세요.
벡터 저장소 초기화와 테이블 생성
PGEngine으로 연결을 확보했다면, 이제 본격적으로 벡터 저장소를 만들 차례입니다. 여기서 중요한 건 테이블 구조를 먼저 정의하는 겁니다. ainit_vectorstore_table 메서드를 사용하면 벡터 데이터를 저장할 테이블을 자동으로 생성해줍니다.
테이블 이름은 마음대로 정할 수 있고, vector_size로 임베딩 벡터의 차원을 지정합니다. OpenAI의 text-embedding-ada-002 모델을 쓴다면 1536차원이 되겠죠. 여기에 metadata_columns 매개변수로 추가 컬럼도 정의할 수 있습니다. 예를 들어 문서의 길이를 저장하는 정수형 컬럼을 만들 수도 있어요.
테이블이 준비되면 PGVectorStore.create 메서드로 벡터 저장소 인스턴스를 생성합니다. 이때 엔진, 테이블 이름, 임베딩 서비스를 파라미터로 넘겨주면 됩니다. 메타데이터 컬럼을 정의했다면 metadata_columns에도 해당 컬럼명을 명시해야 합니다.
흥미로운 점은 기존 테이블을 재활용할 수도 있다는 겁니다. content_column, embedding_column, metadata_json_column 같은 파라미터로 컬럼명을 커스터마이징할 수 있어요. 만약 이미 운영 중인 데이터베이스가 있다면 스키마를 바꾸지 않고도 벡터 저장소로 활용할 수 있습니다.
문서를 임베딩으로 변환하기
이제 실제 데이터를 넣을 단계입니다. 문서를 벡터로 변환하는 과정을 임베딩이라고 하는데, Langchain-postgres는 이 작업을 정말 간단하게 처리해줍니다.
먼저 임베딩 모델이 필요합니다. OpenAI, Cohere, HuggingFace 등 다양한 모델을 선택할 수 있는데, 예를 들어 OpenAIEmbeddings를 쓴다면 API 키만 설정하면 바로 사용 가능합니다. 텍스트를 입력하면 자동으로 벡터로 변환해주는 거죠.
문서를 추가하는 방법도 여러 가지입니다. aadd_documents 메서드는 Document 객체 리스트를 받아서 자동으로 임베딩을 생성하고 데이터베이스에 저장합니다. 각 문서에 고유 ID를 부여할 수도 있고, 메타데이터도 함께 저장할 수 있습니다.
만약 이미 임베딩 벡터가 있다면 aadd_embeddings 메서드를 사용하면 됩니다. 텍스트와 임베딩 벡터를 쌍으로 넘겨주면 되는데, 이건 대량의 데이터를 배치로 처리할 때 유용합니다. 외부에서 임베딩을 미리 생성해놓고 한 번에 삽입하는 식으로 쓸 수 있거든요.
from_documents라는 정적 메서드도 있는데, 이건 문서 리스트와 임베딩 모델만 넘겨주면 저장소 생성부터 문서 추가까지 한 번에 처리해줍니다. 빠르게 프로토타입을 만들 때 정말 편합니다.
유사도 검색으로 관련 데이터 찾기
임베딩을 저장했으니 이제 검색해볼 차례입니다. 벡터 데이터베이스의 진짜 매력은 바로 유사도 검색에 있습니다. 단어가 정확히 일치하지 않아도 의미가 비슷하면 찾아주는 거죠.
가장 기본적인 메서드는 asimilarity_search입니다. 검색 쿼리를 입력하면 자동으로 임베딩으로 변환해서 가장 유사한 문서들을 반환합니다. k 파라미터로 결과 개수를 조절할 수 있고, 기본값은 4개입니다.
조금 더 자세한 정보가 필요하다면 asimilarity_search_with_score를 사용하세요. 이건 문서와 함께 유사도 점수도 같이 반환합니다. 점수를 보면 얼마나 관련성이 높은지 판단할 수 있어서 결과 필터링할 때 유용합니다.
거리 계산 방식도 선택할 수 있습니다. 코사인 유사도가 기본값인데, 내적이나 유클리드 거리를 쓸 수도 있어요. distance_strategy 파라미터로 설정하면 됩니다. 각 방식마다 특성이 다르니까 데이터 특성에 맞게 선택하면 됩니다.
메타데이터 필터링도 지원합니다. 특정 조건을 만족하는 문서만 검색하고 싶을 때 필터를 걸 수 있거든요. 예를 들어 특정 카테고리나 날짜 범위에 속하는 문서만 찾는 식입니다. 이게 일반 벡터 데이터베이스와 PostgreSQL을 사용하는 큰 차이점이에요. SQL의 강력한 쿼리 기능을 그대로 활용할 수 있습니다.
Retriever로 더 편리하게
검색을 더 간편하게 하려면 Retriever를 사용하는 게 좋습니다. as_retriever 메서드를 호출하면 벡터 저장소를 Retriever 객체로 변환할 수 있는데, 이건 LangChain의 다른 컴포넌트들과 연결하기 쉽게 만들어줍니다.
Retriever는 검색 전략을 세밀하게 조정할 수 있습니다. search_type을 similarity로 설정하면 일반 유사도 검색이고, mmr로 하면 Maximum Marginal Relevance 알고리즘을 사용합니다. MMR은 결과의 다양성을 보장해주는데, 비슷비슷한 문서만 나오는 걸 방지해줍니다.
search_kwargs로 세부 파라미터도 조절 가능합니다. k로 결과 개수를 정하고, MMR을 쓴다면 fetch_k로 후보 문서 개수를, lambda_mult로 다양성과 관련성의 균형을 조절합니다.
RAG(Retrieval-Augmented Generation) 시스템을 구축한다면 Retriever는 필수입니다. LLM에 컨텍스트를 제공할 때 Retriever로 관련 문서를 먼저 찾아서 프롬프트에 포함시키는 방식이거든요. LangChain의 RetrievalQA 같은 체인과 바로 연결해서 쓸 수 있어서 정말 편합니다.
하이브리드 검색으로 더 정확하게
최신 버전의 강력한 기능 중 하나가 바로 하이브리드 검색입니다. 벡터 검색만으로는 부족할 때가 있거든요. 예를 들어 특정 키워드가 반드시 포함된 문서를 찾고 싶은데 의미적 유사성도 고려해야 한다면 어떻게 할까요?
HybridSearchConfig를 사용하면 시맨틱 검색과 키워드 검색을 동시에 수행할 수 있습니다. PostgreSQL의 전문 검색 기능과 벡터 검색을 결합하는 건데, 둘의 장점을 모두 취할 수 있어서 검색 품질이 크게 향상됩니다.
구조화된 메타데이터와 벡터를 함께 활용하는 것도 가능합니다. 앞서 말했듯이 metadata_columns로 별도 컬럼을 정의하면 SQL 필터를 벡터 검색과 조합할 수 있습니다. 날짜, 카테고리, 숫자 데이터 등을 활용해서 더 정교한 검색이 가능해지는 거죠.
비동기 처리의 중요성
Langchain-postgres 0.0.16의 모든 주요 메서드는 비동기를 지원합니다. 메서드 이름 앞에 a가 붙은 걸 보셨을 겁니다. aadd_documents, asimilarity_search 같은 식으로요.
비동기가 왜 중요할까요? 대량의 문서를 처리하거나 여러 쿼리를 동시에 실행할 때 성능 차이가 엄청납니다. 동기 방식은 한 작업이 끝날 때까지 기다려야 하지만, 비동기는 여러 작업을 동시에 처리할 수 있거든요. 특히 I/O 작업이 많은 데이터베이스 연동에서는 비동기가 필수입니다.
실제 프로덕션 환경에서는 await 키워드와 함께 사용하면 됩니다. 파이썬의 asyncio를 활용하면 효율적인 병렬 처리가 가능합니다.
마이그레이션과 주의사항
기존에 PGVector를 사용하던 분들이 있다면 마이그레이션을 고려해야 합니다. 스키마 구조가 바뀌었기 때문에 테이블을 새로 만들고 데이터를 다시 입력해야 합니다. 자동 마이그레이션 기능은 아직 없으니 주의하세요.
연결 문자열도 바뀌었습니다. 이전에는 postgresql+psycopg2를 썼다면 이제는 postgresql+psycopg나 postgresql+asyncpg를 사용해야 합니다. 드라이버가 완전히 바뀐 거죠.
보안 측면에서도 개선된 부분이 있습니다. 테이블 생성과 일반 사용 권한을 분리할 수 있게 되어서 최소 권한 원칙을 적용하기 쉬워졌습니다. 애플리케이션 계정에는 읽기/쓰기 권한만 주고, 스키마 변경은 별도 관리자 계정으로 하는 식입니다.
실전 활용 팁
개발하면서 알게 된 몇 가지 팁을 공유할게요. 먼저 임베딩 모델 선택이 중요합니다. 한국어 데이터를 다룬다면 다국어 모델을 쓰는 게 좋고, 영어만 있다면 특화된 모델이 더 나을 수 있습니다.
청크 크기도 신경 써야 합니다. 문서를 너무 작게 쪼개면 맥락이 사라지고, 너무 크면 검색 정확도가 떨어집니다. 보통 500~1000토큰 정도가 적당하고, 20퍼센트 정도 겹치게 하는 게 좋습니다.
인덱스 설정도 잊지 마세요. 데이터가 많아지면 검색 속도가 느려질 수 있는데, 적절한 인덱스를 만들면 해결됩니다. 벡터 컬럼에는 HNSW 인덱스가 효과적이고, 메타데이터 컬럼에는 일반 B-tree 인덱스를 거는 게 좋습니다.
Langchain-postgres 0.0.16은 PostgreSQL을 벡터 데이터베이스로 활용하고 싶은 개발자들에게 정말 유용한 도구입니다. PGVectorStore의 직관적인 API와 강력한 기능들 덕분에 AI 애플리케이션 개발이 한결 수월해졌습니다. 기존 인프라를 그대로 활용하면서 벡터 검색 기능을 추가할 수 있다는 점도 큰 장점이고요.
처음에는 설정할 게 많아 보여도 막상 써보면 정말 편합니다. 문서화도 잘 되어 있고 커뮤니티도 활발해서 막히는 부분이 있어도 금방 해결할 수 있습니다. RAG 시스템을 구축하든, 시맨틱 검색을 구현하든, Langchain-postgres는 든든한 기반이 되어줄 겁니다.