14.2 Embedding 압축과 캐시 활용
OpenAI의 Embedding API를 활용하여 유사 문서 검색, 문맥 확장, 추천 시스템 등을 구축할 때 가장 중대한 고려사항 중 하나는 “비용”과 “속도”입니다. Embedding은 일반적으로 적지 않은 크기의 벡터(예: 1536차원)를 생성하고, 이런 벡터를 수천 개~수백만 개 저장하고 활용하는 과정에서 상당한 컴퓨팅 자원과 비용이 발생합니다.
본 절에서는 OpenAI의 Embedding API를 사용할 때 비용을 절약하고, 응답 속도를 향상시키는 핵심 전략으로 “Embedding 압축”과 “캐시(Cache) 활용” 기법을 소개합니다.
1. 왜 Embedding 압축과 캐시가 중요한가?
1.1 비용 절감
Embedding API는 호출 비용이 있다.
예: text-embedding-3-small은 1,000 tokens당 $0.00002
대량 문서 저장 시 10만 개 텍스트만으로도 상당한 비용 발생
동일한 문서에 대해 매번 Embedding을 생성하는 것은 낭비이다.
1.2 속도 향상
Embedding은 최대 수백 ms~1초의 처리 시간이 필요할 수 있다.
실시간 검색/사용자 응답에 쓰일 경우, 사전 계산과 캐싱이 필요하다.
1.3 반복적인 벡터 작업
동일한 텍스트 조각을 여러 번 Embedding 할 필요는 없다.
전처리와 캐싱 전략 도입 시 API 호출 횟수를 획기적으로 줄일 수 있다.
2. Embedding 압축 전략
Embedding 압축은 원래의 벡터 표현을 손상시키지 않으면서 차원 수나 스토리지 점유량을 줄이기 위해 사용하는 테크닉입니다. 다음과 같은 방식으로 구현합니다.
2.1 PCA(주성분 분석) 기반 차원 축소
Embedding 벡터는 일반적으로 1536차원이지만, PCA 알고리즘을 사용해 256~512로 줄일 수 있음
scikit-learn의 PCA 예시:
from sklearn.decomposition import PCA
import numpy as np
# 기존 1536차원 벡터 예제들
embedding_matrix = np.array([...]) # (N, 1536)
pca = PCA(n_components=256) # 256차원으로 압축
compressed_embeddings = pca.fit_transform(embedding_matrix)
장점: 검색 효율 향상, 벡터 DB 저장 용량 및 I/O 감소
단점: 정보 손실 발생 가능성
2.2 양자화 (Quantization)
float32 → float16 또는 int8로 변환해 벡터 크기 축소
Faiss 벡터 검색 라이브러리는 INT8/OPQ PQ 등 다양한 압축 기법 지원
import faiss
d = 1536 # 원본 차원
nbits = 8 # 각 값당 비트 수 (8비트 INT)
quantizer = faiss.IndexFlatL2(d)
index = faiss.IndexIVFPQ(quantizer, d, 100, 64, nbits)
장점: 빠른 검색 및 RAM 사용량 감소
단점: 검색 정확도 다소 저하 가능
3. Embedding 캐시 전략
Embedding 캐시는 "한 번 생성한 Embedding은 다시 생성하지 않는다"는 원칙을 지키기 위한 저장 기법입니다. 특히 아래 상황에서 유용합니다.
같은 문서를 여러 번 Embedding해야 할 경우
유사한 프롬프트나 요청에서 반복적으로 쓰이는 문구가 존재할 경우
3.1 캐시 키 설계
텍스트 그대로를 key로 사용
혹은 텍스트의 해시(hash 값을 key로 사용) → 속도 및 메모리 이점
import hashlib
import json
def text_to_hash(text: str) -> str:
return hashlib.sha256(text.encode('utf-8')).hexdigest()
cache = {}
def get_embedding(text: str):
key = text_to_hash(text)
if key in cache:
return cache[key]
else:
# OpenAI API 호출 (예시)
response = openai.Embedding.create(input=text, model="text-embedding-3-small")
emb = response['data'][0]['embedding']
cache[key] = emb
return emb
3.2 캐시 저장소 구조
In-Memory: Python dict, Redis
Disk: JSON 파일, Pickle, SQLite
Production 환경: Redis, Weaviate, Qdrant, Pinecone 등에 캐시 벡터 저장 가능
In-Memory (ex. dict)
빠르고 간단
메모리 제한, 휘발성
Redis, Memcached
빠른 외부 저장
설치, 운영 필요
JSON, Pickle
디스크 저장, 간단
검색 속도 느림
DB(SQLite, Redis)
Index 가능, 확장성
추가 개발 필요
3.3 TTL 및 만료 전략
캐시가 무한히 커지는 것을 방지
자주 사용되는 Embedding 위주로 오래 보존
Redis의 TTL(Time-To-Live) 기능 적극 활용 가능
예: 자주 검색되는 문장은 30일 만료 시간 지정 → 메모리 압박 완화
4. Embedding 저장 포맷 최적화
Embedding 벡터를 캐시에 저장할 때는 가독성과 크기를 고려한 적절한 포맷이 필요합니다.
추천 저장 포맷은 다음과 같습니다.
JSON: 직렬화 쉬움, 텍스트 기반. 예:
{
"6c7d...1b": [0.021, -0.003, 0.998, ..., -0.112]
}
Pickle: 파이썬 전용, 빠름. 단, 이식성 ↓
SQLite/Redis: (hash, vector) 형태로 저장. 확장성 ↑
5. 캐시 + 압축 통합 전략
Embedding 생성 시 다음 체계를 따르는 것이 이상적입니다:
1️⃣ 텍스트 → 해시 생성 2️⃣ 캐시 확인 (존재 시 압축된 벡터 반환) 3️⃣ 없으면 API 호출 → 벡터 생성 4️⃣ PCA/양자화 수행 → 압축 저장 5️⃣ 캐시에 저장 (TTL 포함 가능)
캐시가 일정 용량을 초과할 경우:
LRU (Least Recently Used) 방식 적용
일정 기간 이상 접속 없는 항목 삭제
6. 실전 예: 문서형 검색 시스템 캐싱 구조
다음은 실제 PDF 문서들을 분할하여 Embedding하고 저장하는 경우에 적용할 수 있는 캐싱 구조 예시입니다.
# pseudocode
for chunk in document_chunks:
key = hash(chunk) # 캐시용 키 생성
if key in cache:
vector = cache[key]
else:
vector = embed(chunk) # OpenAI 호출
vector = pca.transform(vector) # 압축
cache[key] = vector # 저장
upload_to_vector_db(vector) # FAISS / Pinecone 등
7. 주의사항
동일 텍스트에 대해 항상 동일 Embedding이 나오는 것은 모델에 따라 다름
text-embedding-3 계열은 deterministic (동일 벡터 반환)
이전 모델은 클러스터 기반 일부 비결정성이 존재 가능
압축률을 너무 높이면 검색 정밀도가 급감
캐시 관리 기능은 서비스 초기 단계부터 설계에 포함해야 함
8. 정리 및 Best Practice
PCA
차원 축소
scikit-learn, Faiss
Quantization
벡터 압축
Faiss
캐싱
재활용
dict, Redis, JSON
TTL 적용
메모리 제한
Redis TTL, LRU 알고리즘
벡터 + ID 매핑
빠른 식별
해시(key) 기반 저장 구조
➡️ 비용과 속도를 아끼면서 정확한 결과를 원한다면: ⎡Embedding 캐시 + PCA 압축 + 적절한 TTL⎤의 조합을 초기 아키텍처에 반영하는 것을 권장합니다.
이 절을 통해 Embedding을 대규모로 활용할 때 비용과 리소스를 효과적으로 관리하는 방법을 학습했습니다. 다음 절에서는 대량 호출 환경에서 Embedding 검색이나 Chat Completion API 호출을 최적화하는 고급 팁과 병렬 처리 기법 등을 소개합니다.
Last updated