Pinecone 벡터 DB 무료 셋업과 OpenAI 임베딩 연동 실습


FAISS로 버티다 Pinecone으로 넘어간 진짜 이유

RAG 챗봇을 로컬 FAISS로 운영하다가 한 가지 문제가 생겼다. 문서가 5만 건을 넘어가자 인덱스 파일이 2GB를 넘겼고, 서버를 재시작할 때마다 이 파일을 통째로 메모리에 올리는 데 40초가 걸렸다. 응답 속도가 버텨줄 수 있는 수준이 아니었다.

클라우드 벡터 DB인 Pinecone을 찾아봤다. 무료 플랜이 있었다. 인덱스 1개, 저장 용량 2GB — 개인 프로젝트나 프로토타입 수준에서는 충분했다. OpenAI 임베딩과 연동하는 데 코드 30줄이면 됐다. 셋업부터 실제 유사도 검색까지 직접 검증한 전 과정을 공개한다.



1. 벡터 DB가 필요한 이유와 Pinecone 무료 플랜 조건

일반 데이터베이스는 “정확히 일치하는 값”을 찾는 데 최적화돼 있다. 벡터 DB는 다르다. “의미적으로 가까운 것”을 찾는다. “강아지 사료 추천”이라고 검색했을 때 “반려견 먹이 종류”라는 문서를 찾아주는 게 벡터 검색이다. 텍스트를 숫자 벡터(임베딩)로 변환해 저장하고, 질문 벡터와 가장 가까운 벡터를 수학적으로 계산해 꺼내는 구조다.

Pinecone 무료 플랜(Starter)의 조건은 아래와 같다. 프로토타입과 개인 프로젝트에는 충분한 수준이다.

  • 인덱스 수: 1개
  • 저장 용량: 약 100만 벡터 (1536차원 기준 약 2GB)
  • 월 비용: 완전 무료 (신용카드 불필요)
  • 리전: AWS us-east-1 고정 (무료 플랜)

2. Pinecone 계정 생성 및 인덱스 셋업

계정 생성 및 API 키 발급

Pinecone 공식 사이트(pinecone.io)에서 구글 계정으로 가입하면 바로 대시보드로 진입한다. 왼쪽 메뉴 [API Keys]에서 기본 생성된 키를 복사한다. 프로젝트 폴더의 .env 파일에 아래처럼 저장한다.

PINECONE_API_KEY=여기에_발급받은_키
OPENAI_API_KEY=여기에_OpenAI_키

인덱스 생성 — 차원 수가 핵심

인덱스를 만들 때 가장 중요한 설정이 차원 수(dimension)다. OpenAI의 text-embedding-3-small 모델은 1536차원 벡터를 생성한다. 인덱스의 차원 수와 임베딩 모델의 출력 차원이 반드시 일치해야 한다. 한 번 만든 인덱스의 차원은 변경할 수 없으므로 처음에 정확히 맞춰야 한다.

pip install pinecone openai python-dotenv
import os
from pinecone import Pinecone, ServerlessSpec
from dotenv import load_dotenv

load_dotenv()

pc = Pinecone(api_key=os.getenv("PINECONE_API_KEY"))

INDEX_NAME = "my-rag-index"

# 인덱스가 없을 때만 생성
if INDEX_NAME not in pc.list_indexes().names():
    pc.create_index(
        name=INDEX_NAME,
        dimension=1536,          # text-embedding-3-small 출력 차원
        metric="cosine",         # 텍스트 유사도는 코사인 유사도가 적합
        spec=ServerlessSpec(
            cloud="aws",
            region="us-east-1"   # 무료 플랜은 이 리전만 지원
        )
    )
    print(f"인덱스 '{INDEX_NAME}' 생성 완료")
else:
    print(f"인덱스 '{INDEX_NAME}' 이미 존재")

index = pc.Index(INDEX_NAME)

3. OpenAI 임베딩 생성 및 Pinecone 저장·검색 전체 코드

1단계: 텍스트를 벡터로 변환해 Pinecone에 저장

문서 리스트를 임베딩으로 변환하고 Pinecone에 업서트(upsert)한다. 한 번에 100개씩 배치로 올리는 것이 API 효율 면에서 훨씬 낫다.

from openai import OpenAI

client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))

def get_embedding(text: str) -> list:
    response = client.embeddings.create(
        model="text-embedding-3-small",
        input=text
    )
    return response.data[0].embedding

# 저장할 문서 샘플
documents = [
    {"id": "doc1", "text": "파이썬은 데이터 분석과 AI 개발에 널리 쓰이는 프로그래밍 언어다."},
    {"id": "doc2", "text": "텔레그램 봇은 파이썬으로 30분 만에 만들 수 있다."},
    {"id": "doc3", "text": "Pinecone은 클라우드 기반 벡터 데이터베이스 서비스다."},
    {"id": "doc4", "text": "OpenAI Whisper API는 음성을 텍스트로 변환해주는 STT 모델이다."},
    {"id": "doc5", "text": "GitHub Actions를 활용하면 파이썬 스크립트를 무료로 자동 실행할 수 있다."},
]

# 임베딩 생성 후 Pinecone에 배치 저장
vectors = []
for doc in documents:
    embedding = get_embedding(doc["text"])
    vectors.append({
        "id": doc["id"],
        "values": embedding,
        "metadata": {"text": doc["text"]}  # 원본 텍스트도 함께 저장
    })

index.upsert(vectors=vectors)
print(f"{len(vectors)}개 문서 저장 완료")

2단계: 질문으로 유사 문서 검색

질문을 임베딩으로 변환하고 Pinecone에서 가장 유사한 문서를 꺼내온다. top_k는 반환할 결과 수이고, include_metadata=True를 설정해야 저장해둔 원본 텍스트도 함께 받을 수 있다.

def search_similar(query: str, top_k: int = 3) -> list:
    query_embedding = get_embedding(query)
    results = index.query(
        vector=query_embedding,
        top_k=top_k,
        include_metadata=True
    )
    return results["matches"]

# 검색 테스트
query = "자동화 스크립트를 서버 없이 실행하는 방법"
matches = search_similar(query)

print(f"\n['{query}'] 검색 결과:")
for match in matches:
    score = round(match["score"], 4)
    text = match["metadata"]["text"]
    print(f"  유사도 {score} — {text}")

4. 직접 맞은 에러 2가지와 해결법

에러 ① dimension mismatch — 차원 불일치

인덱스를 처음에 dimension=1536으로 만들었다가 나중에 임베딩 모델을 text-embedding-3-large(3072차원)로 바꿨더니 Vector dimension mismatch 에러가 났다. 이미 만들어진 인덱스의 차원은 바꿀 수 없다. 기존 인덱스를 삭제하고 새로 만들거나, 처음부터 쓸 모델을 확정한 뒤 그에 맞는 차원으로 인덱스를 생성해야 한다. 비용 효율을 생각하면 text-embedding-3-small(1536차원)이 대부분의 프로젝트에 충분하다.

에러 ② upsert 후 바로 query했을 때 결과가 0건

문서를 저장하고 곧바로 검색했는데 결과가 비어 있었다. Pinecone은 upsert 후 인덱싱이 완료되기까지 수 초의 지연이 있다. 저장 직후 바로 쿼리하면 아직 인덱싱이 안 된 벡터는 검색에 잡히지 않는다. time.sleep(3)을 upsert와 query 사이에 넣거나, index.describe_index_stats()로 벡터 카운트가 올라가는 것을 확인한 뒤 검색하면 해결된다.

5. FAISS vs Pinecone 실전 비교

두 가지를 모두 써본 뒤 정리한 기준이다. 프로젝트 규모와 목적에 따라 선택이 달라진다.

비교 항목 FAISS (로컬) Pinecone (클라우드)
비용 완전 무료 무료 플랜 제공 (100만 벡터)
데이터 규모 수만 건까지 쾌적, 그 이상은 메모리 부담 수백만 건도 안정적 처리
서버 재시작 시 인덱스 파일 전체 메모리 로딩 필요 클라우드 상시 유지, 즉시 접근
적합한 용도 로컬 프로토타입, 소규모 RAG 클라우드 배포, 중대규모 RAG 서비스

로컬에서 빠르게 프로토타입을 만들 때는 FAISS가 낫다. 설치도 없고 API 키도 필요 없다. 하지만 서비스를 클라우드에 올리거나 데이터가 수십만 건을 넘어가면 Pinecone으로 넘어가는 게 맞다. 두 가지 모두 써본 지금은 개발 초기에는 FAISS, 배포 단계에서 Pinecone으로 마이그레이션하는 흐름을 기본 패턴으로 쓰고 있다.