따라하기 — 인덱스를 저장하고 다시 불러오기
한 줄 요약
인덱스를 파일로 저장해 두면 다음부터는 불러오기만 하면 됩니다. 이걸 안 하면 실행할 때마다 임베딩을 다시 하며 시간과 비용을 버립니다 — 8장에서 가져다 쓸 때도 마찬가지고요.
1. 저장하지 않으면 어떻게 되나
아래는 개념 설명용 코드입니다 — 실습 파일에 넣지 않습니다.
# 저장 안 하는 버전
vs = FAISS.from_documents(chunks, emb) # 매번 임베딩 15번
retriever = vs.as_retriever(...)
이러면 파일을 실행할 때마다 임베딩을 다시 합니다.
ch06_rag_agent.py 실행 → 임베딩 15번, 수십 초
ch06_rag_agent.py 다시 실행 → 임베딩 15번, 수십 초
ch08_multi_agent.py 실행 → 임베딩 15번, 수십 초 (ch06을 가져다 쓰므로)
우리 문서는 15조각이라 수십 초로 끝납니다. 10,000조각이면 몇 시간입니다. 그리고 매번 비용이 나갑니다.
2. 저장하고 불러오는 코드
아래는 code/ch06_rag_agent.py 의 일부입니다 — 위에서 이미 붙여 넣었으니 다시 넣지 마세요.
def build_index():
emb = get_embeddings()
if INDEX_DIR.exists(): # ← 이미 있나?
print(f" 기존 인덱스를 불러옵니다 ({INDEX_DIR.name}) — 임베딩을 다시 하지 않습니다")
return FAISS.load_local(str(INDEX_DIR), emb, allow_dangerous_deserialization=True)
print(" 인덱스를 새로 만듭니다 — 처음 한 번만 시간이 걸립니다")
...
vs = FAISS.from_documents(chunks, emb)
vs.save_local(str(INDEX_DIR)) # ← 저장
return vs
패턴은 단순합니다.
있으면 → 불러온다 (빠름)
없으면 → 만들고 저장한다 (느림, 한 번만)
이런 방식을 캐시(cache) 라고 부릅니다. "한 번 계산한 것을 보관해 뒀다 재사용한다"는 뜻입니다.
3. 두 번의 실행 비교
첫 실행
인덱스를 새로 만듭니다 — 처음 한 번만 시간이 걸립니다
문서 8쪽 로드
조각 15개로 분할
인덱스 저장 완료 → .../data/policy_index
두 번째부터
기존 인덱스를 불러옵니다 (policy_index) — 임베딩을 다시 하지 않습니다
한 줄로 끝납니다. 실습을 반복할 때 이 차이가 큽니다.
4. 저장된 파일
두 개가 짝입니다.
| 파일 | 무엇 |
|---|---|
index.faiss |
검색용 벡터 데이터 |
index.pkl |
조각의 원문과 출처 정보 |
둘 다 있어야 동작합니다. 검색은 벡터로 하고, 찾은 뒤 원문을 꺼내 LLM에게 보여 줘야 하니까요.
5. allow_dangerous_deserialization=True — 이게 뭔가요
아래는 code/ch06_rag_agent.py 의 일부입니다 — 위에서 이미 붙여 넣었으니 다시 넣지 마세요.
FAISS.load_local(str(INDEX_DIR), emb, allow_dangerous_deserialization=True)
이름이 무섭습니다. "위험한 역직렬화를 허용한다" 는 뜻이죠.
index.pkl 은 파이썬의 pickle 형식입니다. pickle 파일은 불러올 때 그 안에 든 코드를 실행할 수 있습니다. 그래서 남이 준 pickle 파일을 여는 것은 위험합니다 — 악의적인 코드가 들어 있을 수 있으니까요.
LangChain은 이 위험을 알리려고 명시적으로 허용 표시를 하게 만들었습니다.
우리는 안전합니다. 이 파일은 바로 앞 줄에서 우리가 직접 만든 것이니까요. 인터넷에서 받은 인덱스 파일이라면 이 옵션을 켜기 전에 한 번 더 생각해야 합니다.
5장 7절의
eval이야기와 같은 종류의 문제입니다 — 남이 만든 것을 실행할 때는 조심한다.
6. 언제 다시 만들어야 하나
인덱스는 만들어진 시점의 문서를 담고 있습니다. 이런 경우 다시 만들어야 합니다.
| 상황 | 다시 만들어야 하나 |
|---|---|
| PDF 내용이 바뀌었다 | ✓ 반드시 |
| PDF를 추가했다 | ✓ 반드시 |
chunk_size 를 바꿨다 |
✓ 반드시 |
| 임베딩 모델을 바꿨다 | ✓ 반드시 |
| 질문만 바꾼다 | ✗ 필요 없음 |
k 를 바꾼다 |
✗ 필요 없음 |
다시 만드는 법은 폴더를 지우는 것입니다.
터미널에서 실행 — 프로젝트 폴더
agentic_ai에서,(agentic)표시를 확인한 뒤.
# macOS
rm -rf data/policy_index
# Windows
rmdir /s /q data\policy_index
그리고 다시 실행하면 새로 만들어집니다.
이게 캐시의 함정입니다. 문서를 고쳤는데 인덱스를 안 고치면, 옛날 내용으로 답합니다. 오류도 나지 않아 잘못된 답인지 알아차리기 어렵습니다.
5장 8절의 그 이야기 — 에이전트가 틀리는 방식은 대개 "오류"가 아니라 "오류 없이 나오는 오답" — 이 여기서도 반복됩니다.
실제 서비스에서는 문서가 바뀌면 자동으로 다시 만들도록 합니다. 파일의 수정 시각을 비교하거나, 문서 관리 시스템에서 변경 알림을 받는 식입니다.
7. 인덱스는 어디에 두나
아래는 code/ch06_rag_agent.py 의 일부입니다 — 위에서 이미 붙여 넣었으니 다시 넣지 마세요.
INDEX_DIR = DATA / "policy_index" # 만들어 둔 인덱스를 보관할 폴더
data/ 안에 두었습니다. 8장에서도 같은 인덱스를 씁니다.
[6장] 인덱스를 만든다
[7장] (RAG는 안 쓰지만 개념이 이어짐)
[8장] ch06_rag_agent 를 import → 인덱스를 불러온다
8장에서 from ch06_rag_agent import policy_tool 을 하면 build_index() 가 다시 실행됩니다. 그때 인덱스가 저장돼 있으면 즉시 불러오고, 없으면 몇십 초를 기다립니다.
그래서 6장에서 저장을 확실히 해 두는 것이 8장을 위한 준비이기도 합니다.
8. 이 패턴은 여기저기 쓰입니다
"비싼 계산은 한 번만 하고 저장한다"는 발상은 RAG에만 있는 게 아닙니다.
| 상황 | 캐시하는 것 |
|---|---|
| RAG | 임베딩 결과 (이 절) |
| 웹 서비스 | 자주 쓰는 DB 조회 결과 |
| 이미지 처리 | 썸네일 |
| 컴파일 | 빌드 산출물 |
공통 함정도 같습니다 — 원본이 바뀌었는데 캐시를 안 지우면 옛날 것이 나옵니다.
스스로 해 보기
data/policy_index를 지우고 다시 실행해 보세요. 시간을 재 보세요.
bash rm -rf data/policy_index time python code/ch06_rag_agent.py
그리고 한 번 더 실행해 시간을 비교하세요.index.pkl을 지우고index.faiss만 남긴 뒤 실행해 보세요. 어떻게 되나요?chunk_size를200으로 바꾸고 인덱스를 안 지운 채 실행해 보세요. 조각 수가 달라지나요? 왜 그럴까요?allow_dangerous_deserialization=True를 지우고 실행해 보세요. 어떤 오류가 나나요?
3번이 이 절의 핵심 실험입니다. 코드를 고쳤는데 결과가 안 바뀌는 상황을 직접 겪어 보세요.
핵심 정리
- 인덱스를 파일로 저장해 두면 다음부터는 불러오기만 하면 됩니다.
- 패턴은 단순합니다 — 있으면 불러오고, 없으면 만들어 저장한다. 이걸 캐시라고 합니다.
- 저장된 파일은
index.faiss(벡터) 와index.pkl(원문·출처) 두 개가 짝입니다. allow_dangerous_deserialization=True는 pickle 파일이 코드를 실행할 수 있어서 붙는 경고입니다. 내가 만든 것만 불러오세요.- 문서·
chunk_size·임베딩 모델이 바뀌면 반드시 다시 만들어야 합니다. 폴더를 지우면 됩니다. - 캐시의 함정 — 원본이 바뀌었는데 안 지우면 오류 없이 옛날 답이 나옵니다.
- 8장에서 이 인덱스를 그대로 씁니다. 여기서 저장해 두는 것이 8장 준비이기도 합니다.