FAISS에서 Pinecone으로 넘어간 솔직한 이유
제가 한창 RAG 챗봇을 로컬 FAISS로 돌릴 때 문서가 5만 건 넘어가면서 인덱스 파일 사이즈가 2GB를 훌쩍 넘었어요. 매번 서버 재시작할 때마다 이 2GB짜리 파일을 통째로 메모리에 올리느라 40초를 꼬박 잡아먹었는데, 솔직히 그 40초 동안 손 놓고 있으면 미칠 지경이더라고요. 게다가 그 시간 동안은 챗봇 응답이 아예 멈춰서 사용자 경험이 완전 쓰레기였어요.
그래서 클라우드 벡터 DB를 찾다가 Pinecone 무료 플랜을 발견했는데, 인덱스 1개, 저장 용량 2GB 제한인데도 신용카드 등록도 안 하고 공짜로 쓸 수 있어서 솔직히 반신반의하면서 바로 테스트해봤습니다. OpenAI 임베딩과 연결하는 코드가 딱 30줄이라 너무 간단해 보여서 바로 도전했죠. 셋업부터 검색까지 실제로 돌려본 경험을 최대한 디테일하게 정리할게요.
1. 벡터 DB가 왜 필요한지, Pinecone 무료 플랜의 현실적 한계
사실 저는 일반 DB가 딱 맞는 값을 찾는 데는 최적화돼 있다고 생각해요. 근데 텍스트처럼 의미가 비슷한 걸 찾아야 할 땐 벡터 DB가 필수입니다. 예를 들어 “강아지 사료 추천”이라고 검색하면, “반려견 먹이 종류” 같은 유사 문서를 뽑아내는 게 벡터 검색인데, 텍스트를 1536차원 숫자 벡터(임베딩)로 바꿔서 저장하고, 질문 벡터와 가장 가까운 걸 수학적으로 찾는 방식입니다.
Pinecone 무료 플랜(Starter)의 조건은 다음과 같은데, 솔직히 저는 몇 가지 아쉬움이 컸어요.
- 인덱스 수: 딱 1개만 만들 수 있어서 여러 프로젝트 동시 운영은 불가능
- 저장 용량: 약 100만 벡터, 1536차원 기준으로 대략 2GB까지 지원
- 월 비용: 0원, 신용카드 등록도 안 함
- 리전: AWS us-east-1 고정. 서울에서 접속하면 100~200ms 정도 지연이 느껴져서 모바일 테스트할 때 답답했습니다
특히 저장 용량 2GB가 한계라 데이터가 조금만 더 늘어나도 바로 막히는데, 이건 진짜 제 프로젝트에서는 치명적이었어요. 그리고 AWS us-east-1 리전 고정이라 서울에서 쓸 때 네트워크 지연이 불가피해서, 모바일에서 메뉴가 꼬이거나 반응이 느려서 30분 넘게 디버깅하다가 결국 리전 문제라는 걸 알게 됐습니다. 짜증나는 경험이었죠.
2. Pinecone 계정 생성과 인덱스 셋업 과정
계정 만들고 API 키 발급받기
Pinecone 공식 사이트(pinecone.io)에서 구글 계정으로 가입하자마자 대시보드가 뜨는데, 저는 가입 후 30초 만에 왼쪽 메뉴 [API Keys]에서 기본 키를 복사했습니다. 그리고 프로젝트 루트에 .env 파일을 만들어 아래처럼 저장했어요.
PINECONE_API_KEY=여기에_발급받은_키
OPENAI_API_KEY=여기에_OpenAI_키
인덱스 생성 — 차원 수 맞추는 게 30분 삽질 막는 핵심
인덱스 만들 때 가장 중요한 게 차원 수(dimension)입니다. OpenAI text-embedding-3-small 모델이 1536차원 벡터를 뱉으니, 인덱스도 1536으로 꼭 맞춰야 해요. 저는 처음에 차원 개념 모르고 1536으로 인덱스 만들었는데, 나중에 3072차원 모델로 바꾸려다 Vector dimension mismatch 에러로 30분 날렸습니다. 차원이 다르면 인덱스는 무조건 새로 만들어야 하니, 이건 꼭 주의하세요.
필요한 라이브러리 설치는 이 커맨드 한 줄이면 끝납니다.
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" # 무료 플랜은 US 동부 리전만 지원
)
)
print(f"인덱스 '{INDEX_NAME}' 생성 완료")
else:
print(f"인덱스 '{INDEX_NAME}' 이미 존재")
index = pc.Index(INDEX_NAME)
3. OpenAI 임베딩 생성부터 Pinecone 저장·검색까지 코드 전격 공개
1단계: 텍스트 벡터로 변환해 Pinecone에 저장하기
문서 리스트를 임베딩으로 변환해 Pinecone에 올릴 때 저는 한 번에 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. 제가 직접 맞은 에러 두 가지와 해결법
에러 ① dimension mismatch — 차원 불일치
이건 진짜 삽질이었는데, 인덱스를 1536차원으로 만들어놓고 나중에 임베딩 모델을 3072차원인 text-embedding-3-large로 바꾸면서 Vector dimension mismatch 오류가 떴어요. 1시간 이상 헤매다가 결국 인덱스는 차원 변경 불가라 새로 만들어야 한다는 걸 깨달았습니다. 비용 아끼려고 작은 차원 버전으로 통일하는 게 낫겠더군요.
에러 ② upsert 직후 바로 query하면 결과 0건
문서를 업서트하고 바로 검색했는데 결과가 하나도 안 나와서 뭔가 했더니 Pinecone은 업서트 후 인덱싱이 완전히 끝날 때까지 2~3초 지연이 있더라고요. 저는 time.sleep(3) 넣으니까 딱 해결됐어요. 아니면 index.describe_index_stats()로 벡터 개수가 올라갔는지 확인하는 방법도 있습니다.
5. FAISS와 Pinecone, 내 프로젝트에 딱 맞는 선택은?
두 가지를 모두 써본 뒤 정리한 기준이다. 프로젝트 규모와 목적에 따라 선택이 달라진다.
| 비교 항목 | FAISS (로컬) | Pinecone (클라우드) |
|---|---|---|
| 비용 | 완전 무료 | 무료 플랜 제공 (100만 벡터) |
| 데이터 규모 | 수만 건까지 쾌적, 그 이상은 메모리 부담 | 수백만 건도 안정적 처리 |
| 서버 재시작 시 | 인덱스 파일 전체 메모리 로딩 필요 | 클라우드 상시 유지, 즉시 접근 |
| 적합한 용도 | 로컬 프로토타입, 소규모 RAG | 클라우드 배포, 중대규모 RAG 서비스 |
로컬에서 빠르게 프로토타입을 만들 때는 FAISS가 낫다. 설치도 없고 API 키도 필요 없다. 하지만 서비스를 클라우드에 올리거나 데이터가 수십만 건을 넘어가면 Pinecone으로 넘어가는 게 맞다. 두 가지 모두 써본 지금은 개발 초기에는 FAISS, 배포 단계에서 Pinecone으로 마이그레이션하는 흐름을 기본 패턴으로 쓰고 있다.